<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Bordeaux Coders]]></title><description><![CDATA[Bordeaux Coders]]></description><link>https://bordeauxcoders.com</link><generator>RSS for Node</generator><lastBuildDate>Tue, 21 Apr 2026 06:52:18 GMT</lastBuildDate><atom:link href="https://bordeauxcoders.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Create an Azure-Ready GitHub Repository using Pulumi]]></title><description><![CDATA[Creating an application and deploying it to Azure is not complicated. You write some code on your machine, do some clicks in the Azure portal, or run some Azure CLI commands from your terminal and that's it: your application is up and running in Azur...]]></description><link>https://bordeauxcoders.com/create-an-azure-ready-github-repository-using-pulumi</link><guid isPermaLink="true">https://bordeauxcoders.com/create-an-azure-ready-github-repository-using-pulumi</guid><category><![CDATA[GitHub]]></category><category><![CDATA[Pulumi]]></category><category><![CDATA[Azure]]></category><category><![CDATA[Infrastructure as code]]></category><category><![CDATA[github-actions]]></category><dc:creator><![CDATA[Alexandre Nedelec]]></dc:creator><pubDate>Thu, 27 Jul 2023 09:00:13 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1689930992730/ac14726f-0829-43c0-ada0-bd19c6061140.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Creating an application and deploying it to Azure is not complicated. You write some code on your machine, do some clicks in the Azure portal, or run some Azure CLI commands from your terminal and that's it: your application is up and running in Azure.</p>
<p>Yet, that's not real life, at least not what you will do when working on a professional project. Your code needs to be versioned and pushed to a location where your colleagues can work on it. The provisioning of Azure resources and deployment to Azure should be carried out using a properly configured CI/CD pipeline with the necessary authorization.</p>
<p>That's a lot of work that would need to be done each time you start a new project. So let's automate that using Pulumi to simplify the process and create an "<em>Azure-Ready GitHub repository</em>".</p>
<h2 id="heading-whats-an-azure-ready-github-repository">What's an Azure-Ready GitHub repository?</h2>
<p>"<em>Azure-Ready GitHub repository</em>" is not an official term or concept, it's just something I've come up with to describe a Github repository that has everything correctly configured to provision Azure resources or deploy applications to Azure from a GitHub Actions CI/CD pipeline.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689341532227/b68bf554-9239-43c8-b43c-5519659fdf65.png" alt="Diagram of a GitHub repository  interacting with Azure" class="image--center mx-auto" /></p>
<h3 id="heading-the-github-part">The GitHub part</h3>
<p>On the GitHub side, to have an <em>Azure-Ready GitHub repository</em>, we need:</p>
<ul>
<li><p>the GitHub repository itself (already initialized with a <code>main</code> branch)</p>
</li>
<li><p>the necessary GitHub Actions variables/secrets to authenticate to the correct Azure subscription</p>
</li>
<li><p>a YAML file located in the <code>.github/workflows/</code> folder that contains the CI/CD pipeline that provisions resources in Azure</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689344419837/7afc3373-b03b-4a32-b8fb-0b9c32d25dcc.png" alt="A diagram of the GitHub repository to create." class="image--center mx-auto" /></p>
<h3 id="heading-the-azure-part">The Azure part</h3>
<p>On the Azure side, to have an <em>Azure-Ready GitHub repository,</em> we need:</p>
<ul>
<li><p>the existing Azure subscription to which resources are deployed</p>
</li>
<li><p>an <em>identity</em> in the Azure Active Directory of the desired tenant so that the GitHub CI/CD pipeline can authenticate to Azure and interact with the subscription</p>
<ul>
<li><p>an Azure AD application that represents the GitHub Actions pipeline identity</p>
</li>
<li><p>a Service Principal (related to the Azure AD application) that has the contributor role on the Azure subscription</p>
</li>
<li><p>credentials for the CI/CD pipeline to authenticate to Azure on behalf of this Azure AD application</p>
</li>
</ul>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689344609076/c3c23c45-551e-4839-a475-2f564917f984.png" alt="A diagram of the resources to configure in Azure." class="image--center mx-auto" /></p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text"><strong>Azure Active Directory</strong> has recently been renamed <strong>Microsoft Entra ID</strong> (as of the time of writing). However, I will continue to use the term Azure Active Directory throughout the rest of the article. Please note that both terms refer to the same service.</div>
</div>

<h3 id="heading-the-problem-with-secret-credentials">The problem with secret credentials</h3>
<p>People tend to use secret credentials to authenticate their pipeline to Azure and that's not the best thing to do.</p>
<p>From a security standpoint, depending on secrets always poses a security risk. Even if in that case the secret would be safely stored in a GitHub secret and never exposed publicly, it's still better to avoid secrets when we can.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🔐</div>
<div data-node-type="callout-text">That's precisely why when hosting applications in Azure, we use Managed Identities and IAM roles instead of relying on secrets. Yet, here we can't use Managed Identities for GitHub Actions pipelines.</div>
</div>

<p>From a practical standpoint, depending on secrets can quickly become problematic as they expire and thus require rotation. Of course, you can set up alerting or automate secret rotation but that's something you would prefer to avoid managing.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💬</div>
<div data-node-type="callout-text">I recently encountered a situation in Azure DevOps where a deployment failed due to the expiration of an Azure AD Application secret associated with the Service Connection used in the pipeline, and we were not alerted about it. That's the kind of scenario that can easily happen with secrets and that you want to avoid.</div>
</div>

<p>So what can we do about that?</p>
<p>👉 We can stop using secret credentials and use <a target="_blank" href="https://learn.microsoft.com/en-us/azure/active-directory/workload-identities/workload-identity-federation">Workload identity federation</a> instead. I suggest you have a look at this <a target="_blank" href="https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect">GitHub documentation page</a> as well to better understand how it works but basically, you can remember the following:</p>
<ul>
<li><p>this mechanism relies on Open ID Connect and trust between Azure and GitHub</p>
</li>
<li><p>the GitHub pipeline does not need an Azure AD application secret anymore to authenticate to Azure</p>
</li>
<li><p>it's not an Azure thing only, it's an open standard that also works with other cloud providers and other platforms than Github</p>
</li>
</ul>
<p>To establish the trust relationship between the Azure AD application and the GitHub repository, a <em>Federated Identity Credential</em> must be created in the Azure Active Directory. You can find how to do that manually from the portal in the <a target="_blank" href="https://learn.microsoft.com/en-us/azure/active-directory/workload-identities/workload-identity-federation-create-trust?pivots=identity-wif-apps-methods-azp">documentation</a> but we are going to directly automate that 😉.</p>
<h3 id="heading-the-complete-solution-to-implement">The complete solution to implement</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689347670337/452aaa8b-1c28-4d0b-8eb7-474a8c639ee1.png" alt="A diagram showing the interactions between Azure and GitHub." class="image--center mx-auto" /></p>
<h2 id="heading-why-use-pulumi-in-that-context">Why use Pulumi in that context?</h2>
<p>You might wonder why I chose to automate this process using Pulumi instead of writing a Bash or PowerShell script that would execute commands from the GitHub CLI and the Azure CLI.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">By the way, you should check <a target="_blank" href="https://cli.github.com/">GitHub CLI</a> if you have not done it yet, it's very handy. And if you have read my article about <a target="_blank" href="https://www.techwatching.dev/posts/welcome-azure-cli">Azure CLI</a>, you know it's a very convenient tool as well.</div>
</div>

<p>I think Pulumi is a better choice here because:</p>
<ul>
<li><p>a script is imperative by nature, but declarative infrastructure seems more suitable to avoid dealing with idempotency</p>
</li>
<li><p>Pulumi can interact with both GitHub and Azure using its providers</p>
</li>
<li><p>the code will be easier to write and maintain</p>
</li>
<li><p>the code could be integrated into any application (including a future self-service infrastructure portal) using Pulumi Automation API</p>
</li>
</ul>
<p>In this article, the Pulumi code will be in TypeScript but it would work in any language supported by Pulumi.</p>
<h2 id="heading-automate-the-creation-of-the-azure-ready-github-repository">Automate the creation of the Azure-Ready GitHub Repository</h2>
<h3 id="heading-create-the-pulumi-project">Create the Pulumi project</h3>
<p>Let's start by scaffolding a new Pulumi project using TypeScript:</p>
<pre><code class="lang-powershell">pulumi new typescript <span class="hljs-literal">-n</span> AzureOIDC <span class="hljs-literal">-s</span> dev <span class="hljs-literal">-d</span> <span class="hljs-string">"A program to set up an Azure-Ready GitHub repository"</span>
</code></pre>
<p>This command creates a new pulumi project and stack from the TypeScript template:</p>
<ul>
<li><p>The name of the project "<em>AzureOIDC"</em> is specified using the <code>-n</code> option</p>
</li>
<li><p>The description of the project "<em>A program to set up an Azure-Ready GitHub repository</em>" is specified using the <code>-d</code> option</p>
</li>
<li><p>The stack of the project "<em>dev</em>" is specified using the <code>-s</code> option</p>
</li>
</ul>
<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">By default, the <code>pulumi new</code> command installs the dependencies when creating the project. You can prevent this by specifying the <code>-g</code> option, which is useful when you want to use another package manager than the default one (<code>pnpm</code> instead of <code>npm</code> for instance).</div>
</div>

<p>This project will need 3 different providers:</p>
<ul>
<li><p>the <a target="_blank" href="https://www.pulumi.com/registry/packages/azure-native/">Azure Native provider</a></p>
</li>
<li><p>the <a target="_blank" href="https://www.pulumi.com/registry/packages/azuread/">Azure Active Directory provider</a></p>
</li>
<li><p>the <a target="_blank" href="https://www.pulumi.com/registry/packages/github/">GitHub provider</a></p>
</li>
</ul>
<p>So we can add the following packages to our <code>package.json</code> file:</p>
<ul>
<li><p>@pulumi/azure-native</p>
</li>
<li><p>@pulumi/azuread</p>
</li>
<li><p>@pulumi/github</p>
</li>
</ul>
<h3 id="heading-create-the-repository-on-github">Create the repository on GitHub</h3>
<p>To use the GitHub provider, we have to provide GitHub credentials. For that, we can create a personal access token (I prefer to create a <a target="_blank" href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#fine-grained-personal-access-tokens">fine-grained personal access token</a> although a <a target="_blank" href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic">classic personal access token</a> would also work). Next, we simply set the GitHub token in our Pulumi configuration, and the GitHub provider will automatically use it:</p>
<pre><code class="lang-powershell">pulumi config <span class="hljs-built_in">set</span> github:token XXXXXXXXXXXXXX -<span class="hljs-literal">-secret</span>
</code></pre>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🔐</div>
<div data-node-type="callout-text">Don't forget to include the <code>--secret</code> option when setting sensitive configurations, as this ensures that Pulumi encrypts the information. By doing so, we can safely commit the configuration files without creating security risks.</div>
</div>

<p>Now, it's time to create our GitHub repository!</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> github <span class="hljs-keyword">from</span> <span class="hljs-string">"@pulumi/github"</span>;

<span class="hljs-keyword">const</span> repository = <span class="hljs-keyword">new</span> github.Repository(<span class="hljs-string">"azure-ready-repository"</span>, {
  name: <span class="hljs-string">"azure-ready-repository"</span>,
  visibility: <span class="hljs-string">"public"</span>,
  autoInit: <span class="hljs-literal">true</span>
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> repositoryCloneUrl = repository.httpCloneUrl;
</code></pre>
<p>Pulumi has an <a target="_blank" href="https://www.pulumi.com/docs/concepts/resources/names/#autonaming">auto-naming capability</a> that is very convenient to prevent name collisions or to ensure zero-downtime resource updates. Yet, in this context, I prefer to avoid a random suffix in my GitHub repository name, that's why I am specifying the <code>name</code> property to override the auto-naming behavior.</p>
<p>The last line creates a stack <a target="_blank" href="https://www.pulumi.com/docs/concepts/stack/#outputs">output</a> named <code>repositoryCloneUrl</code> so that we can easily get the URL to clone our newly created repository.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">I wanted the repository to be initialized, that's why I set the <code>autoInit</code> property to <code>true</code> but you should set it to <code>false</code> if you have an existing local git repository that you want to push on this GitHub repository.</div>
</div>

<h3 id="heading-create-the-identity-in-azure-active-directory-for-the-github-actions-workflow">Create the <em>identity</em> in Azure Active Directory for the GitHub Actions workflow</h3>
<p>Creating an Azure AD application and its service principal is not very complicated:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> azuread <span class="hljs-keyword">from</span> <span class="hljs-string">"@pulumi/azuread"</span>;

<span class="hljs-keyword">const</span> aadApplication = <span class="hljs-keyword">new</span> azuread.Application(<span class="hljs-string">"AzureReadyApp"</span>, { displayName: <span class="hljs-string">"Azure Ready App"</span> });
<span class="hljs-keyword">const</span> servicePrincipal = <span class="hljs-keyword">new</span> azuread.ServicePrincipal(<span class="hljs-string">"AzureReadServicePrincipal"</span>, {
  applicationId: aadApplication.applicationId,
});
</code></pre>
<p>The OIDC trust thing is a bit more complex. Fortunately, Microsoft's documentation has a detailed page <a target="_blank" href="https://learn.microsoft.com/en-us/azure/active-directory/workload-identities/workload-identity-federation-create-trust?pivots=identity-wif-apps-methods-azp"><em>Configuring an app to trust an external identity provider</em></a> that explains everything and shows how to add a federated identity for GitHub Actions using the Azure Portal, Azure CLI, or Azure PowerShell.</p>
<p>Let's do the same thing using TypeScript and Pulumi Azure AD provider:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">new</span> azuread.ApplicationFederatedIdentityCredential(<span class="hljs-string">"AzureReadyAppFederatedIdentityCredential"</span>, {
  applicationObjectId: aadApplication.objectId,
  displayName: <span class="hljs-string">"AzureReadyDeploys"</span>,
  description: <span class="hljs-string">"Deployments for azure-ready-repository"</span>,
  audiences: [<span class="hljs-string">"api://AzureADTokenExchange"</span>],
  issuer: <span class="hljs-string">"https://token.actions.githubusercontent.com"</span>,
  subject: pulumi.interpolate<span class="hljs-string">`repo:<span class="hljs-subst">${repository.fullName}</span>:ref:refs/heads/main`</span>,
});
</code></pre>
<p>The <code>subject</code> property is what identifies the repository where the GitHub Actions workflow will be authorized to exchange its GitHub token for an Azure access token. It's worth noting that it will only work if the GitHub Actions workflow is run on the git reference (branch or tag) or the environment you specify in <code>subject</code>. You can also specify that only workflows triggered by a pull request should be authorized. Here, I have used the <code>main</code> branch but I could create multiple Federated Identity Credentials with different subjects if needed.</p>
<p>With this configuration, the GitHub Actions workflow we create next will be able to obtain a valid Azure access token.</p>
<p>If you are interested in gaining a better understanding of how all this works, you can refer to <a target="_blank" href="https://learn.microsoft.com/en-us/azure/active-directory/workload-identities/workload-identity-federation#how-it-works">this diagram</a> from Microsoft's documentation (with GitHub serving as the external identity provider in our case).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689457360989/3aa8356e-4d63-430a-962b-384edc1317fc.png" alt="Sequence diagram explaining Azure OIDC." class="image--center mx-auto" /></p>
<h3 id="heading-authorize-the-service-principal-to-provision-resources-on-the-subscription">Authorize the Service Principal to provision resources on the subscription</h3>
<p>We have created everything we need to get a valid Azure access token, but we still have not authorized the application to provision resources on our subscription.</p>
<p>We can do that by giving the Contributor role to our service principal.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> authorization <span class="hljs-keyword">from</span> <span class="hljs-string">"@pulumi/azure-native/authorization"</span>;
<span class="hljs-keyword">import</span> { azureBuiltInRoles } <span class="hljs-keyword">from</span> <span class="hljs-string">"./builtInRoles"</span>;

<span class="hljs-keyword">new</span> authorization.RoleAssignment(<span class="hljs-string">"contributor"</span>, {
  principalId: servicePrincipal.id,
  principalType: authorization.PrincipalType.ServicePrincipal,
  roleDefinitionId: azureBuiltInRoles.contributor,
  scope: pulumi.interpolate<span class="hljs-string">`/subscriptions/<span class="hljs-subst">${subscriptionId}</span>`</span>,
});
</code></pre>
<p>I intentionally did not declare the variable <code>subscriptionId</code> in the code above. It's because it's up to you to choose how you will provide it. You may want to set it in the configuration and retrieve it from it :</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> config = <span class="hljs-keyword">new</span> pulumi.Config();
<span class="hljs-keyword">const</span> subscriptionId = config.get(<span class="hljs-string">"subscriptionId"</span>);
</code></pre>
<p>Or your might want to retrieve it from the current configuration of the Azure native provider :</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> azureConfig = pulumi.output(authorization.getClientConfig());
<span class="hljs-keyword">const</span> subscriptionId = azureConfig.subscriptionId;
</code></pre>
<p>Concerning, the contributor role definition identifier, I could have dynamically retrieved it using Azure APIs (like <a target="_blank" href="https://github.com/pulumi/examples/blob/master/azure-ts-call-azure-sdk/index.ts">here</a>). But honestly, as these identifiers don't change it's much easier to hardcode it in a dedicated <code>builtInRoles.ts</code> file.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> azureBuiltInRoles = {
  contributor : <span class="hljs-string">"/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c"</span>
};
</code></pre>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Please note that you don't have to work on the subscription scope. If you prefer to assign the contributor role (or any other role) to an existing resource group rather than the entire subscription, you can certainly do that as well.</div>
</div>

<h3 id="heading-add-the-configuration-for-the-github-actions-workflow">Add the configuration for the GitHub Actions workflow</h3>
<p>The next step is to correctly set the configuration for the GitHub Actions of our Azure-Ready GitHub repository.</p>
<p>The workflow requires three pieces of information for the OIDC authentication to function properly:</p>
<ol>
<li><p>The identifier of the Azure tenant</p>
</li>
<li><p>The identifier of the Azure subscription</p>
</li>
<li><p>The application identifier (also known as client ID) of the previously created Azure AD application</p>
</li>
</ol>
<p>These identifiers are not secrets, they are just identifiers so we could directly set them as GitHub Actions variables like this:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">new</span> github.ActionsVariable(<span class="hljs-string">"tenantId"</span>, {
  repository: repository.name,
  variableName: <span class="hljs-string">"ARM_TENANT_ID"</span>,
  value: azureConfig.tenantId,
});
</code></pre>
<p>However, I like to keep my tenant id and my subscription id private so we will store them in GitHub secrets but that's not mandatory at all.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> azureConfig = pulumi.output(authorization.getClientConfig());

<span class="hljs-keyword">new</span> github.ActionsSecret(<span class="hljs-string">"tenantId"</span>, {
  repository: repository.name,
  secretName: <span class="hljs-string">"ARM_TENANT_ID"</span>,
  plaintextValue: azureConfig.tenantId,
});

<span class="hljs-keyword">new</span> github.ActionsSecret(<span class="hljs-string">"subscriptionId"</span>, {
  repository: repository.name,
  secretName: <span class="hljs-string">"ARM_SUBSCRIPTION_ID"</span>,
  plaintextValue: azureConfig.subscriptionId,
});

<span class="hljs-keyword">new</span> github.ActionsSecret(<span class="hljs-string">"clientId"</span>, {
  repository: repository.name,
  secretName: <span class="hljs-string">"ARM_CLIENT_ID"</span>,
  plaintextValue: aadApplication.applicationId,
});
</code></pre>
<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">Please note that could also use <a target="_blank" href="https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment">environments for deployment</a> and their associated secrets and variables.</div>
</div>

<h3 id="heading-create-the-github-actions-workflow">Create the GitHub Actions workflow</h3>
<p>Everything seems to be properly configured to provision Azure resources from a GitHub Actions workflow in this new repository, except for the workflow itself. The goal here is to have a properly configured pipeline in the repository to get started provisioning Azure infrastructure.</p>
<p>Here is such a pipeline:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">infra</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">workflow_dispatch:</span>

<span class="hljs-attr">permissions:</span>
      <span class="hljs-attr">id-token:</span> <span class="hljs-string">write</span>
      <span class="hljs-attr">contents:</span> <span class="hljs-string">read</span>
<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">provision-infra:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">'Az CLI login'</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">azure/login@v1</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">client-id:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.AZURE_CLIENT_ID</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">tenant-id:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.AZURE_TENANT_ID</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">subscription-id:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.AZURE_SUBSCRIPTION_ID</span> <span class="hljs-string">}}</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">'Run az commands'</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          az account show
          az group list</span>
</code></pre>
<p>This workflow first authenticates to Azure using OIDC with the <code>azure/login</code> action and then performs some Azure CLI commands to interact with Azure resources. That's fine and probably enough to get you started but you surely want to provision your infrastructure using a more declarative solution than an Azure CLI script. So let's see a more interesting pipeline still authenticating via Azure OIDC but using Pulumi to provision the Azure resources.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">infra</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">workflow_dispatch:</span>

<span class="hljs-attr">permissions:</span>
  <span class="hljs-attr">id-token:</span> <span class="hljs-string">write</span>   <span class="hljs-comment"># required for OIDC auth</span>
  <span class="hljs-attr">contents:</span> <span class="hljs-string">read</span>    <span class="hljs-comment"># required to perform a checkout</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">provision-infra:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v3</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">pnpm</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">pnpm/action-setup@v2</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">version:</span> <span class="hljs-string">latest</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Set</span> <span class="hljs-string">node</span> <span class="hljs-string">version</span> <span class="hljs-string">to</span> <span class="hljs-number">18</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-node@v3</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">node-version:</span> <span class="hljs-number">18</span>
          <span class="hljs-attr">cache:</span> <span class="hljs-string">'pnpm'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">dependencies</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">pnpm</span> <span class="hljs-string">install</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Provision</span> <span class="hljs-string">infrastructure</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">pulumi/actions@v4.4.0</span>
        <span class="hljs-attr">id:</span> <span class="hljs-string">pulumi</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">command:</span> <span class="hljs-string">up</span>
          <span class="hljs-attr">stack-name:</span> <span class="hljs-string">dev</span>
        <span class="hljs-attr">env:</span>
          <span class="hljs-attr">ARM_USE_OIDC:</span> <span class="hljs-literal">true</span>
          <span class="hljs-attr">PULUMI_ACCESS_TOKEN:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.PULUMI_ACCESS_TOKEN</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">ARM_CLIENT_ID:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.ARM_CLIENT_ID</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">ARM_TENANT_ID:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.ARM_TENANT_ID</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">ARM_SUBSCRIPTION_ID:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.ARM_SUBSCRIPTION_ID</span> <span class="hljs-string">}}</span>
</code></pre>
<p>A permission section is required with 2 settings (more details <a target="_blank" href="https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#adding-permissions-settings">here</a>):</p>
<ul>
<li><p><code>id-token: write</code> ➡️ needed to request the OIDC token</p>
</li>
<li><p><code>contents: read</code> ➡️ needed to perform checkout action</p>
</li>
</ul>
<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">When you start to specify specific permissions, you have to specify all the permissions you need for the job because the default permissions won't apply anymore.</div>
</div>

<p>The 3 steps following the checkout step are actions to specify the Node.js version to use, install and correctly configure <a target="_blank" href="https://bordeauxcoders.com/series/pnpm-101">pnpm</a>. We assume here the infrastructure will be provisioned using TypeScript (and Pulumi of course) but there would have been similar steps with other runtimes/languages (a <code>setup-dotnet</code> and a <code>dotnet retore</code> action for .NET for instance).</p>
<p>The last action is the Pulumi action to provision the infrastructure by running the <code>pulumi up</code> on the <code>dev</code> stack. We can see that this action uses environment variables whose values are based on the GitHub Actions secrets we defined earlier. To tell Pulumi to use OIDC, we just have to set the <code>ARM_USE_OIDC</code> environment variable to <code>true</code>.</p>
<pre><code class="lang-yaml">        <span class="hljs-attr">env:</span>
          <span class="hljs-attr">ARM_USE_OIDC:</span> <span class="hljs-literal">true</span>
          <span class="hljs-attr">PULUMI_ACCESS_TOKEN:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.PULUMI_ACCESS_TOKEN</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">ARM_CLIENT_ID:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.ARM_CLIENT_ID</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">ARM_TENANT_ID:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.ARM_TENANT_ID</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">ARM_SUBSCRIPTION_ID:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.ARM_SUBSCRIPTION_ID</span> <span class="hljs-string">}}</span>
</code></pre>
<p>A GitHub Actions secret we did not talk about is <code>PULUMI_ACCESS_TOKEN</code> that is a <a target="_blank" href="https://www.pulumi.com/docs/pulumi-cloud/access-management/access-tokens/">Pulumi access token</a> to use Pulumi Cloud as our backend to store the infrastructure state and encrypt secrets. This token should be:</p>
<ol>
<li><p>Created from Pulumi Cloud (following the documentation <a target="_blank" href="https://www.pulumi.com/docs/pulumi-cloud/access-management/access-tokens/#personal-access-tokens">here</a>)</p>
</li>
<li><p>Stored in the stack configuration using the following command <code>pulumi config set pulumiTokenForRepository ******* --secret</code></p>
</li>
<li><p>Stored in a GitHub Actions secret using this code</p>
<pre><code class="lang-typescript"> <span class="hljs-keyword">new</span> github.ActionsSecret(<span class="hljs-string">"pulumiAccessToken"</span>, {
   repository: repository.name,
   secretName: <span class="hljs-string">"PULUMI_ACCESS_TOKEN"</span>,
   plaintextValue: config.requireSecret(<span class="hljs-string">"pulumiTokenForRepository"</span>),
 });
</code></pre>
</li>
</ol>
<p>The last thing to do is to add this workflow file to the GitHub repository:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { readFileSync } <span class="hljs-keyword">from</span> <span class="hljs-string">"fs"</span>;

<span class="hljs-keyword">const</span> pipelineContent = readFileSync(<span class="hljs-string">"main.yml"</span>, <span class="hljs-string">"utf-8"</span>);
<span class="hljs-keyword">new</span> github.RepositoryFile(<span class="hljs-string">"pipelineRepositoryFile"</span>, {
  repository: repository.name,
  branch: <span class="hljs-string">"main"</span>,
  file: <span class="hljs-string">".github/workflows/main.yml"</span>,
  content: pipelineContent,
  commitMessage: <span class="hljs-string">"Add preconfigured pipeline file"</span>,
  commitAuthor: <span class="hljs-string">"Alexandre Nédélec"</span>,
  commitEmail: <span class="hljs-string">"15186176+TechWatching@users.noreply.github.com"</span>,
  overwriteOnCreate: <span class="hljs-literal">true</span>,
});
</code></pre>
<p>This code:</p>
<ol>
<li><p>reads the <code>main.yml</code> file that contains the workflow we saw previously</p>
</li>
<li><p>creates a file with this content in the repository in the <code>.github/workflows/</code> folder for the GitHub Actions workflows</p>
</li>
<li><p>makes a commit when creating the file (or modifying it)</p>
</li>
</ol>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💬</div>
<div data-node-type="callout-text">To read the YAML file, I use the <code>readFileSync</code> method from the File System API <code>fs</code>. That's one of the things I love about Pulumi: you use the things you already know and that already exist in your ecosystem. No need to look for a module or wait for someone to write one, there is probably something standard or a popular community library you can use.</div>
</div>

<h2 id="heading-test-the-azure-ready-github-repository">Test the Azure-Ready GitHub Repository</h2>
<p>Now that the infrastructure code to provision the Azure-Ready GitHub repository is written, let's run it with the <code>pulumi up</code> command and see if it works!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689845721780/8da1eceb-7911-4b0e-9747-ffaf10dff647.png" alt="Ouput of the pulumi up command with all the resources created" class="image--center mx-auto" /></p>
<p>All the resources are correctly created and our new GitHub repository is ready to be used.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689845903645/533a9d7c-360f-4747-9cc1-b1deb836b4fc.png" alt="Picture of the Azure Ready GitHub repository" class="image--center mx-auto" /></p>
<p>Let's clone it.</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> https://github.com/TechWatching/azure-ready-repository; <span class="hljs-built_in">cd</span> azure-ready-repository
</code></pre>
<p>We want to verify that the GitHub project is properly configured and can provision Azure resources from its GitHub Actions workflow.</p>
<p>Let's add some infrastructure code that provisions a few Azure resources to check that:</p>
<pre><code class="lang-bash">pulumi new azure-typescript -n <span class="hljs-string">"AzureReadyGitHuRepository"</span> -y --force
</code></pre>
<p>The <code>--force</code> option allows us to create the code within a non-empty directory.</p>
<p>I used the <code>azure-typescript</code> template that creates a storage account and outputs retrieve its primary access key.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">🔒</div>
<div data-node-type="callout-text">In the SDK, the outputs of the function that lists the storage access keys are not currently marked as secrets. There is currently an <a target="_blank" href="https://github.com/pulumi/pulumi-azure-native/issues/2408">open issue</a> to change that but in the meantime, I have just modified the code to label the stack output as secret ensuring its encryption.</div>
</div>

<p>Let's run a <code>pnpm install</code> to install the dependencies and generate the <code>pnpm-lock.yaml</code> file. Then, we can push the code to GitHub and run the pipeline to see how it goes.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689849153931/ea3bebff-f580-4966-bd62-6923200f232f.png" alt="Logs of the pipeline run showing that the workflow successfully created a storage account." class="image--center mx-auto" /></p>
<p>That's it, we succeeded to provision a storage account from our new GitHub repository whose creation and configuration were entirely automated using Pulumi.</p>
<h2 id="heading-to-conclude">To conclude</h2>
<h3 id="heading-additional-information">Additional information</h3>
<p>There are different platforms you can use to host your Git repositories: GitHub, GitLab, and Azure DevOps to name a few. We use GitHub in this article but you can easily apply the same logic with other platforms (Pulumi has providers for GitLab and Azure DevOps as well).</p>
<p>Even though the Azure-Ready GitHub repository is provisioned using Pulumi, there's nothing stopping you from using another Infrastructure as Code solution that supports Azure OIDC (such as Azure CLI, which was mentioned in the article, Azure Bicep, or even Terraform) in the GitHub Actions workflow of the created repository. You don't even have to provision infrastructure; you can use this workflow to simply deploy an application to an existing Azure resource.</p>
<h3 id="heading-potential-enhancements">Potential Enhancements</h3>
<p>There are many aspects that could be improved in the infrastructure code provisioning the Azure-Ready GitHub repository, but I believe the current solution serves as a good starting point. Nevertheless, here are some ideas for potential enhancements:</p>
<ul>
<li><p>make additional items, such as the commit author, configurable</p>
</li>
<li><p>authorize an environment and not only a branch to retrieve an Azure token</p>
</li>
<li><p>use environment variables/secrets instead of variable/secrets at the repository scope</p>
</li>
</ul>
<p>I think it would be interesting as well to put that code behind an API or a Web application using Pulumi Automation API to have a self-service solution to create Azure-Ready GitHub repository on the fly.</p>
<h3 id="heading-related-articles">Related articles</h3>
<p>Here are some articles on the same topic I wanted to mention:</p>
<ul>
<li><p><a target="_blank" href="https://leebriggs.co.uk/blog/2022/01/23/gha-cloud-credentials"><strong>Stop using static cloud credentials in GitHub Actions</strong></a> <strong>by Lee Briggs</strong><br />  <strong>➡️</strong> This post provides examples for configuring OIDC authentication with GitHub Actions for AWS, Azure, and GCP. The code for Azure is quite similar to the code I showed here. Yet, it doesn't go so far as to initialize a pipeline ready to deploy resources with Pulumi. Anyway, it's awesome to have the code for all 3 major providers.</p>
</li>
<li><p><a target="_blank" href="https://xaviergeerinck.com/2023/05/16/configuring-github-actions-to-azure-authentication-with-oidc/"><strong>Configuring GitHub Actions to Azure authentication with OIDC</strong></a> <strong>by Xavier Geerinck</strong><br />  <strong>➡️</strong>This post also shows how to configure OIDC authentication with GitHub Actions and Azure but using an Azure CLI script. Although the GitHub repository creation and configuration are done manually, automating the Azure part with a few lines of script is nice.</p>
</li>
<li><p><a target="_blank" href="https://samcogan.com/getting-rid-of-passwords-for-deployment-with-pulumi-oidc-support/"><strong>Getting Rid of Passwords for Deployment with Pulumi OIDC Support</strong></a> <strong>by Sam Cogan</strong><br />  ➡️ If you don't care about automating everything and simply want to configure OIDC authentication through the Azure portal, that's the post you will want to read. There is also an example of a pipeline to provision Azure infrastructure using a .NET Pulumi program.</p>
</li>
</ul>
<h3 id="heading-complete-code-solution">Complete code solution</h3>
<p>In this article, I aimed to provide a step-by-step explanation of how to automate the creation of a GitHub repository with a properly configured workflow to interact with Azure using OpenID Connect. Consequently, the article turned out to be quite lengthy. I apologize for that, but I didn't want to present the code without adequate explanation.</p>
<p>Anyway, now that we've covered everything, here is the complete code, which is just 75 lines long:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> pulumi <span class="hljs-keyword">from</span> <span class="hljs-string">"@pulumi/pulumi"</span>;
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> github <span class="hljs-keyword">from</span> <span class="hljs-string">"@pulumi/github"</span>;
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> azuread <span class="hljs-keyword">from</span> <span class="hljs-string">"@pulumi/azuread"</span>;
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> authorization <span class="hljs-keyword">from</span> <span class="hljs-string">"@pulumi/azure-native/authorization"</span>;
<span class="hljs-keyword">import</span> { azureBuiltInRoles } <span class="hljs-keyword">from</span> <span class="hljs-string">"./builtInRoles"</span>;
<span class="hljs-keyword">import</span> { readFileSync } <span class="hljs-keyword">from</span> <span class="hljs-string">"fs"</span>;

<span class="hljs-keyword">const</span> config = <span class="hljs-keyword">new</span> pulumi.Config();

<span class="hljs-keyword">const</span> repository = <span class="hljs-keyword">new</span> github.Repository(<span class="hljs-string">"azure-ready-repository"</span>, {
  name: <span class="hljs-string">"azure-ready-repository"</span>,
  visibility: <span class="hljs-string">"public"</span>,
  autoInit: <span class="hljs-literal">true</span>
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> repositoryCloneUrl = repository.httpCloneUrl;

<span class="hljs-keyword">const</span> aadApplication = <span class="hljs-keyword">new</span> azuread.Application(<span class="hljs-string">"AzureReadyApp"</span>, { displayName: <span class="hljs-string">"Azure Ready App"</span> });
<span class="hljs-keyword">const</span> servicePrincipal = <span class="hljs-keyword">new</span> azuread.ServicePrincipal(<span class="hljs-string">"AzureReadyServicePrincipal"</span>, {
  applicationId: aadApplication.applicationId,
});
<span class="hljs-keyword">new</span> azuread.ApplicationFederatedIdentityCredential(<span class="hljs-string">"AzureReadyAppFederatedIdentityCredential"</span>, {
  applicationObjectId: aadApplication.objectId,
  displayName: <span class="hljs-string">"AzureReadyDeploys"</span>,
  description: <span class="hljs-string">"Deployments for azure-ready-repository"</span>,
  audiences: [<span class="hljs-string">"api://AzureADTokenExchange"</span>],
  issuer: <span class="hljs-string">"https://token.actions.githubusercontent.com"</span>,
  subject: pulumi.interpolate<span class="hljs-string">`repo:<span class="hljs-subst">${repository.fullName}</span>:ref:refs/heads/main`</span>,
});

<span class="hljs-keyword">const</span> azureConfig = pulumi.output(authorization.getClientConfig());
<span class="hljs-keyword">const</span> subscriptionId = azureConfig.subscriptionId;

<span class="hljs-keyword">new</span> authorization.RoleAssignment(<span class="hljs-string">"contributor"</span>, {
  principalId: servicePrincipal.id,
  principalType: authorization.PrincipalType.ServicePrincipal,
  roleDefinitionId: azureBuiltInRoles.contributor,
  scope: pulumi.interpolate<span class="hljs-string">`/subscriptions/<span class="hljs-subst">${subscriptionId}</span>`</span>,
});

<span class="hljs-keyword">new</span> github.ActionsSecret(<span class="hljs-string">"tenantId"</span>, {
  repository: repository.name,
  secretName: <span class="hljs-string">"ARM_TENANT_ID"</span>,
  plaintextValue: azureConfig.tenantId,
});

<span class="hljs-keyword">new</span> github.ActionsSecret(<span class="hljs-string">"subscriptionId"</span>, {
  repository: repository.name,
  secretName: <span class="hljs-string">"ARM_SUBSCRIPTION_ID"</span>,
  plaintextValue: azureConfig.subscriptionId,
});

<span class="hljs-keyword">new</span> github.ActionsSecret(<span class="hljs-string">"clientId"</span>, {
  repository: repository.name,
  secretName: <span class="hljs-string">"ARM_CLIENT_ID"</span>,
  plaintextValue: aadApplication.applicationId,
});

<span class="hljs-keyword">new</span> github.ActionsSecret(<span class="hljs-string">"pulumiAccessToken"</span>, {
  repository: repository.name,
  secretName: <span class="hljs-string">"PULUMI_ACCESS_TOKEN"</span>,
  plaintextValue: config.requireSecret(<span class="hljs-string">"pulumiTokenForRepository"</span>),
});

<span class="hljs-keyword">const</span> pipelineContent = readFileSync(<span class="hljs-string">"main.yml"</span>, <span class="hljs-string">"utf-8"</span>);
<span class="hljs-keyword">new</span> github.RepositoryFile(<span class="hljs-string">"pipelineRepositoryFile"</span>, {
  repository: repository.name,
  branch: <span class="hljs-string">"main"</span>,
  file: <span class="hljs-string">".github/workflows/main.yml"</span>,
  content: pipelineContent,
  commitMessage: <span class="hljs-string">"Add preconfigured pipeline file"</span>,
  commitAuthor: <span class="hljs-string">"Alexandre Nédélec"</span>,
  commitEmail: <span class="hljs-string">"15186176+TechWatching@users.noreply.github.com"</span>,
  overwriteOnCreate: <span class="hljs-literal">true</span>,
});
</code></pre>
<p>You can find the complete source code used for this article <a target="_blank" href="https://github.com/TechWatching/AzureOIDC">in this GitHub repository</a>.</p>
<p>I hope you enjoyed this article. Please feel free to share your thoughts in the comments, ask questions, or make suggestions. Keep learning.</p>
]]></content:encoded></item><item><title><![CDATA[Handling Package Vulnerabilities in Web Projects with pnpm]]></title><description><![CDATA[In today's fast-paced web projects, with ever-changing technologies, tools, and dependencies, it can be challenging to keep everything up to date. Some may argue that this isn't a significant concern, as long as the project works as expected at a giv...]]></description><link>https://bordeauxcoders.com/handling-package-vulnerabilities-in-web-projects-with-pnpm</link><guid isPermaLink="true">https://bordeauxcoders.com/handling-package-vulnerabilities-in-web-projects-with-pnpm</guid><category><![CDATA[pnpm]]></category><category><![CDATA[Security]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[package manager]]></category><category><![CDATA[Node.js]]></category><dc:creator><![CDATA[Christian Bonnaud]]></dc:creator><pubDate>Thu, 20 Jul 2023 12:25:27 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1689856497420/aeaaa831-9e92-4221-a900-f481d9069880.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In today's fast-paced web projects, with ever-changing technologies, tools, and dependencies, it can be challenging to keep everything up to date. Some may argue that this isn't a significant concern, as long as the project works as expected at a given moment. However, as mentioned in <a target="_blank" href="https://bordeauxcoders.com/ensure-proper-dependency-maintenance">a previous article</a>, keeping dependencies up to date not only ensures stability or optimization but also provides the latest security patches for your application. As you might have guessed, pnpm provides a variety of features to assist you in this regard.</p>
<h2 id="heading-checking-for-vulnerabilities">Checking for vulnerabilities</h2>
<p>First, we have to identify if the project has any known vulnerabilities within its dependencies using the <a target="_blank" href="https://pnpm.io/cli/audit">`audit` command</a>.</p>
<pre><code class="lang-powershell">pnpm audit
</code></pre>
<p>This command checks every package against the <a target="_blank" href="https://github.com/advisories">GitHub Advisory Database</a> and indicates through a report which packages are currently carrying security issues and what is their level of threat.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689715234887/847fd441-1f96-48d5-92f7-7b0a4308ef7b.png" alt="The output of pnpm audit command" class="image--center mx-auto" /></p>
<h2 id="heading-vulnerabilities-resolution">Vulnerabilities resolution</h2>
<p>If the <a target="_blank" href="https://pnpm.io/cli/audit"><code>audit</code> command</a> identifies some issues, you can try to automatically fix them by running the <a target="_blank" href="https://pnpm.io/cli/update"><code>pnpm update</code></a> command. This will try to update each vulnerable package to a safe version, without introducing breaking changes.</p>
<p>If there are still some vulnerabilities after running the update, you can try to use the <a target="_blank" href="https://pnpm.io/package_json#pnpmoverrides">overrides field</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689837842508/18cae59d-5d5d-44c0-89f4-58584ac074ca.png" alt="pnpm section added after running audit --fix" class="image--center mx-auto" /></p>
<p>Simply run the <a target="_blank" href="https://pnpm.io/cli/audit"><code>audit</code> command</a> with the fix option, and it will add a new section in the <code>package.json</code> file indicating the version of the packages to use whenever those packages and their dependency graph are resolved during the installation.</p>
<pre><code class="lang-powershell">pnpm audit -<span class="hljs-literal">-fix</span>
</code></pre>
<p>You can also specify a threat level while fixing the vulnerabilities, for instance, to only fix the critical ones first :</p>
<pre><code class="lang-powershell">pnpm audit -<span class="hljs-literal">-audit</span><span class="hljs-literal">-level</span> critical
</code></pre>
<h3 id="heading-good-to-know">Good to know</h3>
<p>If you are running the <code>pnpm audit</code> command in a CI pipeline, you can output the audit report as a JSON file thanks to the <code>-json</code> option. You can then make available this report as an artifact of your pipeline for future reference.</p>
<hr />
<p>To maintain a secure development environment in the long run, it's crucial to regularly audit your project for vulnerabilities. You should consider running the <code>audit</code> command as part of your continuous integration process or at regular intervals during development.</p>
]]></content:encoded></item><item><title><![CDATA[Who is using pnpm?]]></title><description><![CDATA[You may have come across pnpm through discussions with fellow developers, reading blog posts, watching videos, or attending developer conferences. You have probably heard its praises: it's fast, disk-space efficient, and great for monorepos.
However,...]]></description><link>https://bordeauxcoders.com/who-is-using-pnpm</link><guid isPermaLink="true">https://bordeauxcoders.com/who-is-using-pnpm</guid><category><![CDATA[pnpm]]></category><category><![CDATA[package manager]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Node.js]]></category><dc:creator><![CDATA[Alexandre Nedelec]]></dc:creator><pubDate>Thu, 06 Jul 2023 09:00:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1688499829832/a05253bc-76c1-4390-93aa-24f5298ca7b8.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You may have come across pnpm through discussions with fellow developers, reading blog posts, watching videos, or attending developer conferences. You have probably heard its praises: it's fast, disk-space efficient, and great for monorepos.</p>
<p>However, you might wonder: who is actually using pnpm?</p>
<h2 id="heading-a-growing-popularity">A growing popularity</h2>
<p>At the time of writing, pnpm has over 24k stars on GitHub, and this number is rapidly increasing. The pnpm Twitter account maintains a <a target="_blank" href="https://twitter.com/pnpmjs/status/856484673572261888">thread</a> that tracks the number of stars. Each time the GitHub repository gains 1k stars, a new tweet is posted. For quite some time now, it has been growing by 1K every two months.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/pnpmjs/status/1666004997840986116">https://twitter.com/pnpmjs/status/1666004997840986116</a></div>
<p> </p>
<p>Another indicator of its growing popularity is its number of downloads. If you go to <a target="_blank" href="https://npm-stat.com/charts.html?package=pnpm&amp;package=yarn&amp;package=npm&amp;from=2017-01-04&amp;to=2023-07-04">npm stats</a> you can see how this number evolved compared to npm and yarn.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688505611869/6af55f8c-6301-4c85-b79f-05e114b5ba2f.webp" alt="npm vs yarn vs pnpm downloads per day" class="image--center mx-auto" /></p>
<p>I believe this diagram speaks for itself 🚀.</p>
<h2 id="heading-which-companies-are-using-pnpm">Which companies are using pnpm?</h2>
<p>There is a <a target="_blank" href="https://pnpm.io/users">page</a> on pnpm's documentation about well-known companies using pnpm.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688506335296/83a8fe0b-bb15-4070-9512-49cb3d2de9e3.webp" alt="Screeshot of the documentation showing companies using pnpm" class="image--center mx-auto" /></p>
<p>You can also see some other companies on the <a target="_blank" href="https://stackshare.io/pnpm">StackShare website</a> (but it seems not many companies took the time to fill in the fact that they were using pnpm in their stack).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688506923492/2a6caaa6-1cd9-42fe-a489-4dc0e2055739.png" alt="Screeshot of the StackShare page showing companies using pnpm" class="image--center mx-auto" /></p>
<h2 id="heading-which-popular-open-source-projects-are-using-pnpm">Which popular open-source projects are using pnpm?</h2>
<p>If you see a <code>pnpm-lock.yaml</code> or a <code>pnpm-workspace.yaml</code> file in a GitHub repository, then that project is definitively using pnpm to manage its dependencies. You can use this technique to find GitHub projects using pnpm by querying them with <a target="_blank" href="https://github.com/search?q=path%3A**%2Fpnpm-lock.yaml&amp;type=code">GitHub code search</a>.</p>
<p>I thought it would be interesting to explore which package managers are utilized in the development of popular JavaScript framework projects. And guess what? Many JavaScript frameworks are developed using pnpm 💖.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">To check that these projects were using pnpm, I not only verify the presence of pnpm specific files but also checked their continuous integration pipelines (contained in the <code>.github</code> folder) to see what they were using to manage their dependencies.</div>
</div>

<p>Here is a non-exhaustive list of popular JavaScript web frameworks that use pnpm as their package manager:</p>
<ul>
<li><p><a target="_blank" href="https://github.com/vuejs/core">Vue</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/nuxt/nuxt">Nuxt</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/vercel/next.js/">Next.js</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/sveltejs/kit">SvelteKit</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/solidjs/solid-start">SolidStart</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/withastro/astro">Astro</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/BuilderIO/qwik">Qwik</a></p>
</li>
</ul>
<p>That's quite an impressive list: most modern JavaScript web frameworks seem to have chosen pnpm. That's also the case for popular frontend tooling projects like <a target="_blank" href="https://github.com/vitejs/vite">Vite</a> or <a target="_blank" href="https://github.com/vercel/turbo">Turbo</a>.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">The fact that pnpm is utilized by maintainers for internal development of these frameworks does not imply that these frameworks can only be used with pnpm. Typically, JavaScript frameworks are "package manager" agnostic, allowing you to use your preferred package manager when developing a project with one of these frameworks.</div>
</div>

<h2 id="heading-should-you-use-pnpm-because-others-do">Should you use pnpm because others do?</h2>
<p>Short answer: no.</p>
<p>Choosing a technology solely based on its popularity is not advisable. While popularity is a factor to consider, it should not be the only determining aspect. Thus, you should not use pnpm because well-known companies or popular open-source projects use it.</p>
<p>However (and here's the long answer 😉), you should consider exploring pnpm, as there must be a reason why all these intelligent individuals have chosen it over npm or yarn. Investigate the issues pnpm resolves for them; perhaps you face similar challenges in your projects. See what problems pnpm solves for them, maybe you have the same problems in your projects. You might not even be aware of certain problems (such as lengthy CI due to time-consuming package installations, excessive space occupied by node modules, or issues with hoisted node modules), but pnpm could potentially make some things easier. Nevertheless, if you are satisfied with your current package manager, there is no need to switch just to imitate the popular frameworks projects.</p>
<p>I believe people are familiar with npm since it is the default package manager for Node.js projects. They might also know about yarn because it was initially developed by Facebook (who created React) and addressed some issues with npm. However, people recognize and utilize pnpm due to its performance and ability to resolve the problems they might encounter with npm package management. That's also why I use pnpm; it does the job, and it does it quickly.</p>
<p>Now you know that you're not alone in using pnpm; from renowned companies to popular open-source projects, many people are utilizing it.</p>
]]></content:encoded></item><item><title><![CDATA[Ensure Proper Dependency Maintenance]]></title><description><![CDATA[When it comes to writing code, projects often depend on multiple external libraries, keeping dependencies up to date is crucial for ensuring stability, security, and performance. In this article, we will explore the importance of dependency maintenan...]]></description><link>https://bordeauxcoders.com/ensure-proper-dependency-maintenance</link><guid isPermaLink="true">https://bordeauxcoders.com/ensure-proper-dependency-maintenance</guid><category><![CDATA[pnpm]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[package manager]]></category><category><![CDATA[Security]]></category><dc:creator><![CDATA[Christian Bonnaud]]></dc:creator><pubDate>Thu, 22 Jun 2023 09:00:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1687362352929/3bdcbfdb-b9f3-4c7a-93bb-db1aa82f9346.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When it comes to writing code, projects often depend on multiple external libraries, keeping dependencies up to date is crucial for ensuring stability, security, and performance. In this article, we will explore the importance of dependency maintenance and delve into the <a target="_blank" href="https://pnpm.io/cli/outdated"><code>pnpm outdated</code></a> command.</p>
<h3 id="heading-why-is-it-important-to-keep-your-dependencies-up-to-date">Why is it important to keep your dependencies up to date?</h3>
<p>It could be tempting to just keep your whole project with its dependencies just as it is after submitting your pull request or deploying your new feature in production. It's currently compiling, the code has been tested on other environments, and everything's working just fine. So why should you bother spending more time later to check for updates, apply them and adjust your code accordingly? Well, there are a few reasons for that:</p>
<ol>
<li><p><strong>Security</strong>: Regularly updating dependencies ensures that security vulnerabilities are patched, keeping your application safe from potential exploits.</p>
</li>
<li><p><strong>Bug Fixes and Stability</strong>: Updates to dependencies often include fixes for bugs, but also improve the stability and performance of your code.</p>
</li>
<li><p><strong>New Features and Optimization</strong>: Updated dependencies may introduce new features and optimizations that can improve your application's functionality and performance.</p>
</li>
<li><p><strong>Future Upgrade Simplification</strong>: It is much easier to regularly update dependencies and manage small changes than to perform large upgrades with breaking changes.</p>
</li>
</ol>
<h3 id="heading-exploring-the-pnpm-outdated-command">Exploring the "pnpm outdated" command</h3>
<p>The <a target="_blank" href="https://pnpm.io/cli/outdated"><code>pnpm oudated</code></a> command simplifies the process of identifying outdated dependencies within your project.</p>
<pre><code class="lang-bash">pnpm outdated
</code></pre>
<p>By running this command, you'll get a list of dependencies that need to be updated, along with other information, such as the current version, the latest version available, and also any potential compatibility issues, in a friendly format. Here's an example of the output of the command:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1687290956578/d7bef100-1fae-42af-ae81-00640b37c538.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-good-to-know">Good to know</h2>
<p>You can also use the <code>pnpm outdated</code> command for global packages on your machine with the <code>-g</code> option.</p>
<p>Another interesting possibility is to only check a subset of packages you are interested in like this:</p>
<pre><code class="lang-bash">pnpm outdated <span class="hljs-string">"@vue/*"</span>
</code></pre>
<p>Here, we are only checking for outdated packages among those whose names start with <code>@vue/</code>.</p>
<p>Please have a look at <a target="_blank" href="https://pnpm.io/cli/outdated">the documentation page</a> to see every option available.</p>
]]></content:encoded></item><item><title><![CDATA[Vue.js CI/CD: Continuous Integration]]></title><description><![CDATA[Why are we talking about CI in the first place?
When working on a project, you typically focus on a specific feature at a time, making changes on a dedicated branch for that feature. When it's time for you to integrate these modifications into the pr...]]></description><link>https://bordeauxcoders.com/vuejs-cicd-continuous-integration</link><guid isPermaLink="true">https://bordeauxcoders.com/vuejs-cicd-continuous-integration</guid><category><![CDATA[ci-cd]]></category><category><![CDATA[Vue.js]]></category><category><![CDATA[Continuous Integration]]></category><category><![CDATA[CI/CD]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[Alexandre Nedelec]]></dc:creator><pubDate>Mon, 19 Jun 2023 09:30:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1685302381824/ade12bf2-8381-421d-a45c-995eb8addaaf.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-why-are-we-talking-about-ci-in-the-first-place">Why are we talking about CI in the first place?</h2>
<p>When working on a project, you typically focus on a specific feature at a time, making changes on a dedicated branch for that feature. When it's time for you to integrate these modifications into the project's code base, the code base has likely evolved since you began working on your feature, as other team members have also pushed their work. That's why your code changes may introduce errors in the application you are developing.</p>
<p>Regardless of that, while implementing your feature you might have broken some tests, added a security vulnerability, reduced code quality, or simply not adhered to all code conventions used by your team. Even if your colleagues review the code of your Pull Request, they can miss some of these issues. Nonetheless, it would be more efficient for such errors to be detected automatically, enabling people to concentrate their feedback on other aspects.</p>
<p>Continuous Integration enables us to do precisely that: automatically identify potential issues and make the integration of new changes in a project's code base less error-prone.</p>
<p>According to Microsoft:</p>
<blockquote>
<p>Continuous integration (CI) is the process of automatically building and testing code every time a team member commits code changes to <a target="_blank" href="https://learn.microsoft.com/en-us/devops/develop/git/what-is-version-control">version control</a>.</p>
</blockquote>
<h2 id="heading-what-are-the-steps-involved-in-a-ci-pipeline">What are the steps involved in a CI pipeline?</h2>
<p>We often hear discussions about the "Build pipeline" and the "Release pipeline" as if building the application was the only task performed in a continuous integration pipeline. However, this is far from the truth; the "Build" is an important step, but not the only one.</p>
<p>Up until now, we have talked a lot about continuous integration for projects in general but nothing specific for Vue.js. Why is that? Because the steps for continuous integration of a Vue.js project are the same as for any other project:</p>
<ul>
<li><p>Install dependencies</p>
</li>
<li><p>Build the application</p>
</li>
<li><p>Perform code quality static analysis</p>
</li>
<li><p>Perform security analysis</p>
</li>
<li><p>Run tests</p>
</li>
</ul>
<blockquote>
<p>💬 It's good to know that, as part of the build step, an executable artifact is often generated, and then used (in the same pipeline or a CD pipeline) to deploy the application in an environment.</p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1685392580225/1a9d97a6-5b1f-4201-9bcb-eebc12d71743.png" alt class="image--center mx-auto" /></p>
<p>Depending on your project, preferences, and available services, your continuous integration process may vary, but it should include these steps, regardless of the tools you use within them.</p>
<p>There might be additional steps in your Continuous Integration pipeline, but the ones mentioned are the primary ones. Moreover, security is not an optional step; it should be an integral part of your continuous integration.</p>
<h2 id="heading-leveraging-packagejson-for-your-ci-setup">Leveraging package.json for your CI setup</h2>
<p>When you create a Vue.js project using <code>create-vue</code>, the generated <code>package.json</code> file will contain several npm scripts:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1685303272329/66bb6789-80ca-437a-b81a-6b680e7df4c5.png" alt="Screenshot of the npm scripts section of a package.json file." class="image--center mx-auto" /></p>
<p>You can observe that some of these scripts precisely correspond to the necessary steps for the continuous integration pipeline, such as build and unit tests. We will discuss each of them in future articles but the npm scripts in the default <code>create-vue</code> template are definitively a good starting point to set up your CI.</p>
<p>You can see that packages used in the npm scripts are specified in the <code>devDependencies</code> section of the <code>package.json</code> file. That means these packages will be available to use locally or in a CI server after executing the <code>pnpm install</code> command. As part of the CI, other packages may also be needed, so you should include them in the <code>devDependencies</code> section as well.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1685392757121/dddbbd43-b47a-4ac9-b8f0-d50f801610da.png" alt class="image--center mx-auto" /></p>
<p>In your CI pipeline, I think it's a good idea to directly execute the npm scripts of the <code>package.json</code> file rather than specifying the packages you want to run along with their corresponding flags and parameters. You can accomplish this by using the <a target="_blank" href="https://pnpm.io/fr/cli/run"><code>pnpm run</code></a> command like so: <code>pnpm run build</code> or <code>pnpm build</code> (all npm scripts are aliased by pnpm by default). Of course, you'll need to add any missing npm scripts required for your CI. There are several benefits to this approach:</p>
<ul>
<li><p>It simplifies the CI pipeline and makes it easier to read</p>
</li>
<li><p>you won't have to modify your pipeline when you change something in an npm script (whether it's the package you use or just a parameter)</p>
</li>
<li><p>the steps in your CI pipeline will be more consistent across projects (including both Vue.js and non-Vue.js projects) if you always use the same npm script names</p>
</li>
<li><p>the same commands will be executed with the same parameters, whether locally or on a CI server</p>
</li>
</ul>
<p>It's important to note that you should not wait for a CI pipeline execution to detect issues in your code. The sooner you identify and resolve problems, the better. Before pushing your changes, you should run the npm scripts that test your code and perform static analysis on it.</p>
<h2 id="heading-wrapping-it-up">Wrapping it up</h2>
<p>Setting up a Continuous Integration pipeline for your Vue.js project is essential for preventing issues, maintaining code quality, ensuring security, and streamlining the development process. By leveraging the npm scripts of the <code>package.json</code> file you can simplify your CI pipeline and ensure consistency both locally and on the CI server, as well as across your projects.</p>
<p>Future articles in this series will delve into the details of various stages of a continuous integration pipeline (such as using <code>vue-tsc</code> or <code>eslint</code> for static analysis) and their implementation in GitLab CI or GitHub Actions pipelines.</p>
]]></content:encoded></item><item><title><![CDATA[Execute commands using your project dependencies]]></title><description><![CDATA[You have a dependency in your project and want to execute a command using it? The pnpm exec command can help you with that.
An example
 pnpm exec eslint . --ext .ts

Given that ESLint is a project dependency, this example shows how to use the pnpm ex...]]></description><link>https://bordeauxcoders.com/execute-commands-using-your-project-dependencies</link><guid isPermaLink="true">https://bordeauxcoders.com/execute-commands-using-your-project-dependencies</guid><category><![CDATA[pnpm]]></category><category><![CDATA[npm]]></category><category><![CDATA[package manager]]></category><category><![CDATA[tools]]></category><dc:creator><![CDATA[Alexandre Nedelec]]></dc:creator><pubDate>Thu, 15 Jun 2023 09:00:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1685824698376/cccfc407-cc9b-433e-af06-af59c4aa8bf2.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You have a dependency in your project and want to execute a command using it? The <a target="_blank" href="https://pnpm.io/cli/exec">pnpm exec</a> command can help you with that.</p>
<h2 id="heading-an-example">An example</h2>
<pre><code class="lang-bash"> pnpm <span class="hljs-built_in">exec</span> eslint . --ext .ts
</code></pre>
<p>Given that ESLint is a project dependency, this example shows how to use the <code>pnpm exec</code> command to run the ESLint tool on all TypeScript files within the project.</p>
<h2 id="heading-some-use-cases">Some use cases</h2>
<ul>
<li><p>You need to do a specific command that is not part of your npm scripts</p>
</li>
<li><p>You want to execute a tool that is a dependency of your project without having to install it globally</p>
</li>
<li><p>You need to execute a CLI package command in a CI pipeline, and this package is already included in the <code>devDependencies</code> of your project.</p>
</li>
</ul>
<h2 id="heading-good-to-know">Good to know</h2>
<p>If the command you are using does not conflict with a built-in pnpm command, there is no need to specify 'exec'. Referring to the previous example, you can simply run:</p>
<pre><code class="lang-bash"> pnpm eslint . --ext .ts
</code></pre>
<p>It's one of the small details that make using <code>pnpm</code> so pleasant.</p>
]]></content:encoded></item><item><title><![CDATA[Getting Started with Pnpm: Fundamental Commands]]></title><description><![CDATA[As developers, we are constantly looking for tools that can help us to improve our daily work and our productivity. With web projects, package management has been for a long time associated with npm. However, a newer contender, pnpm, offers many bene...]]></description><link>https://bordeauxcoders.com/getting-started-with-pnpm-fundamental-commands</link><guid isPermaLink="true">https://bordeauxcoders.com/getting-started-with-pnpm-fundamental-commands</guid><category><![CDATA[Node.js]]></category><category><![CDATA[pnpm]]></category><category><![CDATA[package manager]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Christian Bonnaud]]></dc:creator><pubDate>Thu, 08 Jun 2023 09:30:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1686174847120/def31cd7-1163-4940-b028-cb03cee602fd.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As developers, we are constantly looking for tools that can help us to improve our daily work and our productivity. With web projects, package management has been for a long time associated with npm. However, a newer contender, pnpm, offers many benefits that make it worth considering. In this article, we will explore the basic commands of pnpm.</p>
<h2 id="heading-initializing-a-new-project">Initializing a new project</h2>
<pre><code class="lang-bash">pnpm init
</code></pre>
<p><a target="_blank" href="https://pnpm.io/cli/init">This command</a> creates a <code>package.json</code> file where each of the project's dependencies and scripts will be referenced.</p>
<h2 id="heading-adding-a-package">Adding a package</h2>
<pre><code class="lang-bash">pnpm add &lt;package-name&gt;
</code></pre>
<p><a target="_blank" href="https://pnpm.io/cli/add">This command</a> simply adds a new package to your project.</p>
<p>One of the main pnpm's benefits over npm or yarn is its disk space efficiency. While npm installs each package separately for every project, pnpm uses a different <a target="_blank" href="https://pnpm.io/motivation">approach</a>: packages are stored in a central location on the disk and shared across projects, resulting in significant disk space savings and faster installations.</p>
<h2 id="heading-installing-all-dependencies">Installing all dependencies</h2>
<pre><code class="lang-bash">pnpm install
</code></pre>
<p>Run <a target="_blank" href="https://pnpm.io/cli/install">this command</a> to install all dependencies for a project.</p>
<h2 id="heading-updating-packages">Updating packages</h2>
<pre><code class="lang-bash">pnpm update &lt;package-name&gt;
</code></pre>
<p>Just like any package manager, <a target="_blank" href="https://pnpm.io/cli/update">this command</a> allows you to specify a specific package version to update or update all packages in your project. With pnpm's shared store, updating packages becomes faster, as it can reuse packages already present on the disk.</p>
<h2 id="heading-removing-packages">Removing packages</h2>
<pre><code class="lang-bash">pnpm remove &lt;package-name&gt;
</code></pre>
<p>To remove a package from your project, just run <a target="_blank" href="https://pnpm.io/cli/remove">this command</a> and specify the name of the package to be removed. Note that if you don't specify the <code>-g</code> option, your package will remain on your disk, ready to be reused within another project.</p>
<h2 id="heading-running-scripts">Running scripts</h2>
<pre><code class="lang-bash">pnpm run &lt;script-name&gt;
pnpm &lt;script-name&gt;
</code></pre>
<p>Defining scripts within the <code>package.json</code> file allows you to have aliases that are easier to manipulate than excessively long CLI commands. You can run them by using <a target="_blank" href="https://pnpm.io/cli/run">the <code>run</code> command</a> and the name of the script. One cool thing is that you can save a few keystrokes by omitting the <code>run</code> keyword, pnpm will automatically look for script aliases.</p>
]]></content:encoded></item><item><title><![CDATA[Introducing the Vue.js CI/CD series]]></title><description><![CDATA[This is the first article of the Vue.js CI/CD series. It will be the opportunity to explain the purpose of the series and the topics we plan to cover.
Why this series?
We delved deeply into CI/CD for Vue.js when preparing a DevOps practices course fo...]]></description><link>https://bordeauxcoders.com/introducing-the-vuejs-cicd-series</link><guid isPermaLink="true">https://bordeauxcoders.com/introducing-the-vuejs-cicd-series</guid><category><![CDATA[ci-cd]]></category><category><![CDATA[Vue.js]]></category><category><![CDATA[Devops]]></category><category><![CDATA[CI/CD]]></category><dc:creator><![CDATA[Alexandre Nedelec]]></dc:creator><pubDate>Thu, 01 Jun 2023 11:59:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1684416071178/1ec20651-19f7-4a10-992b-94449eafeaf8.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This is the first article of the Vue.js CI/CD series. It will be the opportunity to explain the purpose of the series and the topics we plan to cover.</p>
<h2 id="heading-why-this-series">Why this series?</h2>
<p>We delved deeply into CI/CD for Vue.js when preparing a DevOps practices course for students in engineering school. The course wasn't directly related to Vue.js; however, we chose to use a Vue.js application for hands-on exercises focused on implementing CI/CD pipelines. Through this process, we gained valuable insights that we now wish to share.</p>
<p>While there are numerous blog posts on Vue.js, not many articles specifically address setting up CI/CD pipelines for Vue.js projects. Yet, having proper continuous integration and automating deployments are two aspects that should not be neglected in a project. That's the main reason why we decided to write this Vue.js CI/CD series.</p>
<h2 id="heading-what-are-we-going-to-talk-about">What are we going to talk about?</h2>
<p>As you can expect, we will cover the usual topics:</p>
<ul>
<li><p>package management</p>
</li>
<li><p>build &amp; artifacts</p>
</li>
<li><p>static analysis</p>
</li>
<li><p>testing</p>
</li>
<li><p>security</p>
</li>
<li><p>deployment</p>
</li>
</ul>
<p>Examples will be shown using different CI/CD platforms and cloud services.</p>
<h3 id="heading-cicd-platforms">CI/CD platforms</h3>
<p>We can't cover all the CI/CD platforms so we will focus on GitHub Actions and GitLab CI.</p>
<p>Even though each platform has its unique features, the majority of the concepts we will discuss can be applied to other platforms as well. So, don't stop reading the series just because you are using a different platform 😉.</p>
<h3 id="heading-cloud-services">Cloud services</h3>
<p>There are numerous hosting options for a Vue.js application, and we will demonstrate how to deploy an application on at least the following platforms:</p>
<ul>
<li><p>Azure Static Web App</p>
</li>
<li><p>Vercel</p>
</li>
<li><p>Netlify</p>
</li>
</ul>
<h2 id="heading-which-sample-application-will-we-be-using">Which sample application will we be using?</h2>
<p>This series aims to discuss CI/CD for Vue.js applications so that anyone can learn how to set up a CI/CD pipeline for their Vue.js project. That's why we will use the sample code from the basic application generated when creating a new Vue.js project.</p>
<p>And to be clear, when you start a new Vue.js project you don't want to use the Vue CLI because it is in maintenance mode. Instead, you should use <a target="_blank" href="https://github.com/vuejs/create-vue"><code>create-vue</code></a> which is based on Vite and is the recommended way of scaffolding a Vue.js project.</p>
<blockquote>
<p>💬 I think it's important to mention it because I still see new blog posts talking about creating new projects using Vue CLI.</p>
</blockquote>
<p>So nothing specific in the code of the application we will build and deploy, just the basic things you get when you run the <code>pnpm create vue@latest</code> command with:</p>
<ul>
<li><p>TypeScript enabled ➡️ it's 2023, I don't see any valid reason why to choose Vanilla JS instead of TypeScript so if you are not using TypeScript you probably should</p>
</li>
<li><p>Vitest enabled ➡️ the vite-native unit test framework you want to use to test your code</p>
</li>
<li><p>ESLint enabled ➡️ because static analysis should be part of your Continuous Integration pipeline</p>
</li>
</ul>
<p>The last thing to mention: we will use the latest version of <a target="_blank" href="https://pnpm.io/"><code>pnpm</code></a> to manage dependencies. Our preferred package manager is pnpm for various reasons, but the primary one is its remarkable speed!</p>
<blockquote>
<p>💬 You can check the <a target="_blank" href="https://pnpm.io/">pnpm website</a> to read more about pnpm or have a look at our <a target="_blank" href="https://bordeauxcoders.com/series/pnpm-101">pnpm 101 series</a>.</p>
</blockquote>
<p>We hope you will have a great time learning about CI/CD for Vue.js application. See you in the next article.</p>
]]></content:encoded></item><item><title><![CDATA[Manage multiple Node.js versions]]></title><description><![CDATA[If you are working on various projects, you have likely encountered situations where you need to have multiple versions of Node.js installed on your computer.
You might not know it, but managing multiple Node.js versions is something you can do with ...]]></description><link>https://bordeauxcoders.com/manage-multiple-nodejs-versions</link><guid isPermaLink="true">https://bordeauxcoders.com/manage-multiple-nodejs-versions</guid><category><![CDATA[pnpm]]></category><category><![CDATA[package manager]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[npm]]></category><dc:creator><![CDATA[Alexandre Nedelec]]></dc:creator><pubDate>Thu, 25 May 2023 09:00:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1684616725982/ed4807fe-b329-4837-a4a4-176ebc2071e0.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you are working on various projects, you have likely encountered situations where you need to have multiple versions of Node.js installed on your computer.</p>
<p>You might not know it, but managing multiple Node.js versions is something you can do with pnpm, using the <a target="_blank" href="https://pnpm.io/fr/cli/env"><code>pnpm env</code></a> command.</p>
<h2 id="heading-an-example">An example</h2>
<pre><code class="lang-bash">pnpm env use -g lts
</code></pre>
<p>This example demonstrates how to install the LTS version of Node.js.</p>
<p>Additionally, you can install specific versions of Node.js, view the versions already present on your computer, or remove one.</p>
<h2 id="heading-why-use-pnpm-as-your-node-version-manager">Why use pnpm as your Node version manager?</h2>
<p>Because managing your Node.js version is built into pnpm: if you already use pnpm as your npm package manager, you don't need to install another tool.</p>
<p>But there is absolutely nothing wrong with using another Node version manager if you prefer. I was using <a target="_blank" href="https://github.com/coreybutler/nvm-windows">nvm-windows</a> before and I was happy with it. I just don't see the point of installing it anymore as similar functionality is already available in pnpm.</p>
<h2 id="heading-good-to-know">Good to know</h2>
<p>To specify a Node.js version to use in a project/folder, you can add an <a target="_blank" href="https://pnpm.io/fr/npmrc"><code>.npmrc</code></a> file <code>use-node-version</code><a target="_blank" href="https://pnpm.io/fr/npmrc#use-node-version">​</a> setting, like that:</p>
<pre><code class="lang-bash">use-node-version=16.16.0
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Perform Dynamic Execution of an npm Package]]></title><description><![CDATA[Sometimes, all you want to do is grab an npm package and execute a command with it, without having to install it (whether globally or as a dependency).
That's what you can do with pnpm dlx.
An example
pnpm dlx vercel deploy

This example shows how to...]]></description><link>https://bordeauxcoders.com/perform-dynamic-execution-of-an-npm-package</link><guid isPermaLink="true">https://bordeauxcoders.com/perform-dynamic-execution-of-an-npm-package</guid><category><![CDATA[pnpm]]></category><category><![CDATA[npm]]></category><category><![CDATA[package manager]]></category><category><![CDATA[tools]]></category><dc:creator><![CDATA[Alexandre Nedelec]]></dc:creator><pubDate>Thu, 18 May 2023 13:57:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1684414375216/79e83d11-0ed2-4d83-a701-3da856e8b7ed.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Sometimes, all you want to do is grab an npm package and execute a command with it, without having to install it (whether globally or as a dependency).</p>
<p>That's what you can do with <a target="_blank" href="https://pnpm.io/cli/dlx"><code>pnpm dlx</code></a>.</p>
<h2 id="heading-an-example">An example</h2>
<pre><code class="lang-bash">pnpm dlx vercel deploy
</code></pre>
<p>This example shows how to use the <a target="_blank" href="https://vercel.com/docs/cli">vercel CLI package</a> without having to install it thanks to <code>pnpm dlx</code>.</p>
<p>In this example, pnpm downloads the vercel package, and executes it with the command <code>deploy</code> (that deploys a project to the Vercel platform).</p>
<h2 id="heading-some-use-cases">Some use cases</h2>
<ul>
<li><p>You don't want to install globally a package because you only need to execute its binary script once</p>
</li>
<li><p>You don't want a package to be a dev dependency of your project, or you are not using it in the context of a Node project</p>
</li>
<li><p>You need to execute a CLI package command from a CI pipeline</p>
</li>
<li><p>You want to ensure you use the latest version of a package (useful for starter kits like <code>create-vite</code>, or <code>create-vue</code>)</p>
</li>
</ul>
<h2 id="heading-good-to-know">Good to know</h2>
<p>For starter kits, you can use <a target="_blank" href="https://pnpm.io/cli/create"><code>pnpm create</code></a> instead of <code>pnpm dlx</code>. For instance, executing <code>pnpm create vue</code> is equivalent to executing <code>pnpm dlx create-vue</code>.</p>
]]></content:encoded></item></channel></rss>