<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Posts on Haseeb Majid</title>
    <link>https://haseebmajid.dev/posts/</link>
    <description>Recent content in Posts on Haseeb Majid</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en</language>
    <lastBuildDate>Mon, 09 Mar 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://haseebmajid.dev/posts/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>TIL - How to Git Push to Multiple Repositories</title>
      <link>https://haseebmajid.dev/posts/2026-03-09-til-how-to-git-push-to-multiple-repositories/</link>
      <pubDate>Mon, 09 Mar 2026 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2026-03-09-til-how-to-git-push-to-multiple-repositories/</guid>
      <description>&lt;p&gt;I have a personal project that I wanted to push to both tangled.sh (self-hosted) and GitLab. In case something
happened to my personal homelab and backups.&lt;/p&gt;
&lt;p&gt;You can do this but adding another remote i.e. &lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git remote set-url --add --push origin git@gitlab.com:hmajid2301/go-routinely.git
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;Make sure to add both repositories with this command&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Then when you go git push it will push to all repositories.&lt;/p&gt;
&lt;p&gt;You can double check with:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;goroutinely on  fix/ci &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;$&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt; via 🐹 v1.25.5 via ❄  impure &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;nix-shell-env&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;❯ git remote show origin
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Welcome to this knot!
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;* remote origin
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Fetch URL: git@git.haseebmajid.dev:majiy00.tngl.sh/go-routinely
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Push  URL: git@git.haseebmajid.dev:majiy00.tngl.sh/go-routinely
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Push  URL: git@gitlab.com:hmajid2301/go-routinely.git
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  HEAD branch: main
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://stackoverflow.com/questions/14290113/git-pushing-code-to-two-remotes&#34;&gt;https://stackoverflow.com/questions/14290113/git-pushing-code-to-two-remotes&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I have a personal project that I wanted to push to both tangled.sh (self-hosted) and GitLab. In case something
happened to my personal homelab and backups.</p>
<p>You can do this but adding another remote i.e. <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git remote set-url --add --push origin git@gitlab.com:hmajid2301/go-routinely.git
</span></span></code></pre></div><blockquote>
<p>Make sure to add both repositories with this command</p>
</blockquote>
<p>Then when you go git push it will push to all repositories.</p>
<p>You can double check with:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">goroutinely on  fix/ci <span class="o">[</span>$<span class="o">]</span> via 🐹 v1.25.5 via ❄  impure <span class="o">(</span>nix-shell-env<span class="o">)</span>
</span></span><span class="line"><span class="cl">❯ git remote show origin
</span></span><span class="line"><span class="cl">Welcome to this knot!
</span></span><span class="line"><span class="cl">* remote origin
</span></span><span class="line"><span class="cl">  Fetch URL: git@git.haseebmajid.dev:majiy00.tngl.sh/go-routinely
</span></span><span class="line"><span class="cl">  Push  URL: git@git.haseebmajid.dev:majiy00.tngl.sh/go-routinely
</span></span><span class="line"><span class="cl">  Push  URL: git@gitlab.com:hmajid2301/go-routinely.git
</span></span><span class="line"><span class="cl">  HEAD branch: main
</span></span></code></pre></div><div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://stackoverflow.com/questions/14290113/git-pushing-code-to-two-remotes">https://stackoverflow.com/questions/14290113/git-pushing-code-to-two-remotes</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>My Year in Review 2025</title>
      <link>https://haseebmajid.dev/posts/2026-01-01-my-year-in-review-2025/</link>
      <pubDate>Thu, 01 Jan 2026 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2026-01-01-my-year-in-review-2025/</guid>
      <description>&lt;p&gt;Another year has passed, and it&amp;rsquo;s time to reflect on what I accomplished, what I learned, and where I&amp;rsquo;m
heading next. 2025 was a year of shipping projects, learning new technologies, and making some significant
life changes.&lt;/p&gt;
&lt;h2 id=&#34;looking-back-2025-goals-review&#34;&gt;Looking Back: 2025 Goals Review&lt;/h2&gt;
&lt;p&gt;My core aims were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Release a working version of Banter Bus by end of January&lt;/li&gt;
&lt;li&gt;Try to release say 20ish YouTube videos next year&lt;/li&gt;
&lt;li&gt;Look at making some money from my side projects&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I successfully completed the first goal, though the other two took a back seat due to changing priorities.
With regards to YouTube, I didn&amp;rsquo;t really find the motivation to make videos. I will likely have another
crack at it this year, aiming at creating content with simpler editing which takes less effort.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Another year has passed, and it&rsquo;s time to reflect on what I accomplished, what I learned, and where I&rsquo;m
heading next. 2025 was a year of shipping projects, learning new technologies, and making some significant
life changes.</p>
<h2 id="looking-back-2025-goals-review">Looking Back: 2025 Goals Review</h2>
<p>My core aims were:</p>
<ul>
<li>Release a working version of Banter Bus by end of January</li>
<li>Try to release say 20ish YouTube videos next year</li>
<li>Look at making some money from my side projects</li>
</ul>
<p>I successfully completed the first goal, though the other two took a back seat due to changing priorities.
With regards to YouTube, I didn&rsquo;t really find the motivation to make videos. I will likely have another
crack at it this year, aiming at creating content with simpler editing which takes less effort.</p>
<h2 id="fun-metrics">Fun Metrics</h2>
<ul>
<li>📝 <strong>35 blog posts</strong> published</li>
<li>💻 <strong>1,208 commits</strong> across all projects</li>
<li>📚 <strong>7 books</strong> read (fantasy mostly!)</li>
<li>🎤 <strong>2 conference talks</strong> given</li>
</ul>
<h2 id="projects">Projects</h2>
<h3 id="banter-bus">Banter Bus</h3>
<p>Link: <a href="https://banterbus.games">https://banterbus.games</a>
Repo: <a href="https://gitlab.com/hmajid2301/banterbus">https://gitlab.com/hmajid2301/banterbus</a></p>
<p><img
        loading="lazy"
        src="/posts/2026-01-01-my-year-in-review-2025/images/banterbus.png"
        type=""
        alt="Banter Bus"
        
      /></p>
<p>I did manage to release the first version of Banter Bus at the end of January. It was unfortunately in a
broken state. Multiplayer games are hard to test I found out, but it was a good learning experience
nonetheless.</p>
<p>I came back to it in around September/October to help clear the backlog of tasks I had. Like graceful
shutdown. Resuming games on startup and improving the UI. Whilst of course fixing game breaking bugs and a
few dodgy SQL queries. I really enjoyed building this again, with HTMX and Go. It ended up being the basis
for a conference talk I did at GoLabs later in the year as well.</p>
<p>It also became a nice little project to practice some terraform and kubernetes. I deployed it in my
homelab, on a k3s (k8s) cluster leveraging fluxcd for a more automated gitops approach.</p>
<h3 id="saas-voxicle">SaaS (Voxicle)</h3>
<p>Video: <a href="https://www.youtube.com/watch?v=oM9UUdwlaWA">https://www.youtube.com/watch?v=oM9UUdwlaWA</a></p>
<p><img
        loading="lazy"
        src="/posts/2026-01-01-my-year-in-review-2025/images/voxicle.jpg"
        type=""
        alt="Voxicle"
        
      /></p>
<p>I tried to build a feedback collection called voxicle (micro-saas) in public, trying to give daily updates
on bluesky. But eventually I found myself losing focus and interest. So I ended up not enjoying it, I may
go back to it sometime.</p>
<p>The idea was to have a small business on the side to make a bit of income, using my side projects to
generate some cash. But of course it&rsquo;s easier said than done. However the code is all there and the skeleton
to start another SaaS is there. So if I decide to start another one or carry on this one it should be easy
enough.</p>
<h3 id="home-lab">Home Lab</h3>
<p>I carried on working on my homelab. By the end of the year I&rsquo;ve simplified it to a NAS, running TrueNAS and
a single machine (Framework Desktop). Which acts as both my desktop and a homelab self hosting a number of
services. The most useful being, authentik for auth, jellyfin as a media server and gitlab runners for
CI/CD on GitLab.</p>
<p>I worked on creating a mostly declarative k3s cluster, where I could set it up with barely any manual
commands. Such as having a postgres user created for terraform, which we could then use to create databases
for services running the k3s cluster.</p>
<p>Overall I have a long list of services I want to look at self hosting and trying to see if I can use them vs
other offerings. Such as replacing good reads with book lore.</p>
<p>I also added a JetKVM, so I can control my PC remotely if I need to in an emergency. I have it set to auto
boot when it gets power. So it should always switch on in theory.</p>
<p><strong>Current Setup:</strong></p>
<ul>
<li>Framework Desktop</li>
<li>TrueNAS</li>
<li>Jet KVM</li>
</ul>
<h2 id="career--professional-growth">Career &amp; Professional Growth</h2>
<h3 id="career-change">Career Change</h3>
<p>I decided to change jobs after my current employer decided to do some major restructuring and decided it
wasn&rsquo;t really for me anymore. The time spent to get ready to interview and then interview, just seemed to
take me out of routine. This career change was one of the major life events of 2025, and ultimately impacted
my ability to focus on side projects like Voxicle.</p>
<h3 id="conference-talks">Conference Talks</h3>
<p>I did two conference talks last year</p>
<h4 id="observability-made-painless-go-otel--lgtm-stack">Observability Made Painless: Go, OTel &amp; LGTM Stack</h4>
<p>YouTube: <a href="https://www.youtube.com/watch?v=t3Xz-IrxNwk">https://www.youtube.com/watch?v=t3Xz-IrxNwk</a></p>
<p>Learning more about o11y and OTel has proven to be useful. At my job as well I have been able to see a few
gaps and helped to improve the o11y. Especially around tracing and missing spans etc. I&rsquo;m glad I researched
this topic in more detail. Even ignoring the OTel part there is a lot of useful knowledge that is very
transferable.</p>
<h4 id="what-i-learnt-building-a-web-app-with-go-and-htmx">What I learnt building a web app with Go and HTMX</h4>
<p>The video hasn&rsquo;t been released yet on YouTube</p>
<p>But this talk like my other one was basically some learnings I had when building Banter Bus with HTMX and
how others could get started building their own full stack app with HTMX and Go. Plus some other tools I
used that I really liked such as sqlc.</p>
<h2 id="technical-exploration">Technical Exploration</h2>
<h3 id="impermanence-tpm-luks-decryption-and-secure-boot">Impermanence, TPM LUKs decryption and Secure Boot</h3>
<p>Since I didn&rsquo;t have any personal projects later in the year. I decided to spend my time playing around my
Nix config (because I&rsquo;m that cool). On my framework desktop, I finally managed to get impermanence setup.
Such that any files I don&rsquo;t specify to get persisted (or folders) are wiped on reboot. Forcing me to make
more of my config declarative which can be a pain. But makes it much easier to setup a new machine.</p>
<p>I can do a mostly unattended install of my NixOS config, with only a few manual steps post install. Mostly
around enabling secure boot. I also decided to setup LUKs with TPM decryption, which brings its own world of
exploits, such as the PCR 15 not being checked, tricking the TPM. Or the cold boot attack. However, does
mean I don&rsquo;t have to type a password in twice.</p>
<h3 id="niri">Niri</h3>
<p>I decided to try Niri in November after seeing a random
<a href="https://www.youtube.com/watch?v=Q827qLzXGfc">YouTube Video</a>. I decided to try it with noctalia-shell,
which solved a bunch of my issues with running a tiling window manager and missing some of those quick access
utilities I had on gnome. But Niri seems to work pretty well on my Ubuntu 24.04 machine. So I could easily
use the same workflow at work and home.</p>
<p>I like the idea of being able to scroll horizontally, one random use case I started using was with Guild
Wars 2, keeping the wiki open next to it, so I could look up random info I needed quickly.</p>
<p>Overall I am happy moving over to Niri from Hyprland and there wasn&rsquo;t much of a learning curve. I just
copied most of my keybindings over.</p>
<h2 id="personal-growth">Personal Growth</h2>
<h3 id="reading">Reading</h3>
<p>After being inspired by someone I worked with, who had a massive book shelf with tons of fantasy books in
their background whilst on conference calls. I decided to start reading again, when I use to commute pre
covid I got about 60 odd mins of reading in a day. But I stopped, and barely read since. So I decided to
watch a bunch of videos, get some recommendations and have created a massive backlog of mostly fantasy books
I want to read.</p>
<p>This started in about in November and I managed to read almost 8 by the end of the year. I don&rsquo;t expect to
keep this pace, my reading pace normalized to a more sustainable rate towards the end of the year. At first I
was finishing a book every 5 days or so.</p>
<h2 id="looking-forward-2026-goals">Looking Forward: 2026 Goals</h2>
<ul>
<li>Go back to making YouTube videos
<ul>
<li>Recreating my workflow in NixOS</li>
</ul>
</li>
<li>Do one conference talk this year</li>
<li>Continue expanding my homelab with new self-hosted services</li>
<li>Read at least 20 books throughout the year</li>
<li>Find another couple of personal projects</li>
<li>Play Banter Bus with people at work
<ul>
<li>Having the confidence to share it, that it won&rsquo;t break</li>
</ul>
</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>2025 was a year of significant change and growth. While I didn&rsquo;t achieve all my initial goals, I shipped
Banter Bus, changed jobs, gave two conference talks, and rediscovered my love for reading. I&rsquo;ve learned that
it&rsquo;s okay for priorities to shift, and that sometimes the best progress comes from adapting to new
circumstances rather than rigidly sticking to a plan. Here&rsquo;s to making 2026 even better!</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Setup a New PC With Lanzaboote, TPM Decryption, sops-nix, Impermanence and nixos-anywhere</title>
      <link>https://haseebmajid.dev/posts/2025-12-31-how-to-setup-a-new-pc-with-lanzaboote-tpm-decryption-sops-nix-impermanence-nixos-anywhere/</link>
      <pubDate>Wed, 31 Dec 2025 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2025-12-31-how-to-setup-a-new-pc-with-lanzaboote-tpm-decryption-sops-nix-impermanence-nixos-anywhere/</guid>
      <description>&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;details
  class=&#34;notice warning&#34;
  open=&#34;true&#34;
&gt;
    &lt;summary class=&#34;notice-title&#34;&gt;Be Careful&lt;/summary&gt;
  
  &lt;p&gt;Make sure if you follow this guide you could lose your data. Make sure to back up whatever important data you have.
Or do what I did and test this on a new device where it doesn&amp;rsquo;t matter if something goes wrong.&lt;/p&gt;
&lt;p&gt;But don&amp;rsquo;t try to do this on a machine that has important data, or you need to use day to day. Though I was able to
set up a new laptop in about 20 mins once I got everything working. It can be fiddly.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="background">Background</h2>
<details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">Be Careful</summary>
  
  <p>Make sure if you follow this guide you could lose your data. Make sure to back up whatever important data you have.
Or do what I did and test this on a new device where it doesn&rsquo;t matter if something goes wrong.</p>
<p>But don&rsquo;t try to do this on a machine that has important data, or you need to use day to day. Though I was able to
set up a new laptop in about 20 mins once I got everything working. It can be fiddly.</p>

</details>

<p>Since finishing the second round of development on my game/web app banter bus (currently not deployed anywhere since I took down
my k8s cluster), I have been looking for something else to sink my teeth into.</p>
<p>I decided I was going to sell my custom-built PC and then use the money to get a Framework desktop.
So I thought I might as well go with something simpler that would work just as well for development.</p>
<p>As part of this, I decided to finally tackle some of the tasks I wanted to complete with my Nix configuration. Mainly
setting up <a href="https://wiki.nixos.org/wiki/Impermanence">impermanence</a>, which means files we don&rsquo;t persist will get deleted
across reboots, forcing us to specify more of our machine in config/nix code. I haven&rsquo;t done this yet with home-manager,
but will at some point do that as well, though that will take much more effort and also be more dangerous.</p>
<p>Whilst doing this, I decided that I would also like to set up Secure Boot with <a href="https://github.com/nix-community/lanzaboote">Lanzaboote</a>
and LUKS but using TPM decryption, vs needing to type in a LUKS password then another password to login.</p>
<p>I set up my new device using nixos-anywhere, a great tool when you have a well-defined nix config to build from, which
can build and install NixOS on a device if it can SSH to it. My favourite tool for installing NixOS whenever I need to
(probably more often than I&rsquo;d like to admit). Whilst this all worked great and eventually I managed to get my setup
working, I am writing this blog post on my new Framework Desktop. I wondered if we could cut down the number of manual
steps, originally inspired by <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</p>
<p>However, I hit some issues with all of the tools above playing nice. Mostly sops-nix with impermanence, getting
the SSH keys on the machine so I could use my secrets. In this increasingly long blog post, I will show you how I got it
set up. I won&rsquo;t go into lots of detail about each specific tool. I will assume you are somewhat familiar with them.
Each could have their own blog post (or maybe YouTube video; I should start creating videos again, I think).</p>
<p>I will show you the final config I managed, with minimal manual effort needed. This should work on any Framework
device (especially the Secure Boot part).</p>
<h2 id="config">Config</h2>
<p>Now I&rsquo;ll quickly show you how I have set up my Nix config for the various tools above. These configurations are interdependent: impermanence creates the persist structure, sops-nix needs persisted SSH keys for decryption, and Lanzaboote needs persisted signing keys for Secure Boot. Link to my <a href="https://gitlab.com/hmajid2301/nixicle/-/tree/7ffe47fb27c804383d5c53405e918f7b4749bfaf">config</a> (at the point I published this article).</p>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">One Disk</summary>
  
  My setup also assumes you are just setting up a single disk, i.e., one NVMe.
</details>

<h3 id="sops-nix">sops-nix</h3>
<p>When we have secrets that we don&rsquo;t want to keep in plaintext, we can use sops to encrypt the data with our age/SSH keys.
Either host key for NixOS secrets or our age key for home-manager related secrets. For example, for defining the user&rsquo;s
password, which in our secrets.yaml file looks like <code>user_password: ENC[AES256_GCM,data:wSbEwtgPzM1FLYAb3rMCXneXJr2xM4w4lydvwA+DDP1i4DNPKQvC7VUKe00wIp3rDhjLTept/WA8uIGEJsPaf30/iYc2txdDWVvjDL81UnXtIfFoXKsYWQ7vrftShMJckByMiD5uZoYkSA==,iv:0YpQ5RL5CSjfS6jfpZArore25jn42uW3IRr8xLK/798=,tag:oVyxq1uIsKfV/HQWum6LQA==,type:str]</code>. But this can be decrypted by our Nix config and stored at <code>/run/secrets</code> (and other folders next to it).</p>
<p>This means when we do a nixos-anywhere install, we need to send it relevant SSH keys/age keys so that it can decrypt
our secrets such that we can log in. Otherwise, there will be no secret and we won&rsquo;t be able to log in to our machine.</p>
<p>In my <code>hosts/framework/default.nix</code> config I have:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">sops</span><span class="o">.</span><span class="n">secrets</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">user_password</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">sopsFile</span> <span class="o">=</span> <span class="sr">./secrets.yaml</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">neededForUsers</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">user</span><span class="o">.</span><span class="n">passwordSecretFile</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">sops</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">user_password</span><span class="o">.</span><span class="n">path</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>And then when defining users, we can do something like this in <code>modules/nixos/user/default.nix</code>, where we can define the hash of the password above:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">users</span><span class="o">.</span><span class="n">mutableUsers</span> <span class="o">=</span> <span class="no">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">users</span><span class="o">.</span><span class="n">users</span><span class="o">.</span><span class="si">${</span><span class="n">cfg</span><span class="o">.</span><span class="n">name</span><span class="si">}</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">hashedPasswordFile</span> <span class="o">=</span> <span class="n">cfg</span><span class="o">.</span><span class="n">passwordSecretFile</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>And finally my <code>modules/nixos/security/sops/default.nix</code> config:</p>
<p>I reference this file in the persist, which I&rsquo;m not sure I need to do:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">sops</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">age</span><span class="o">.</span><span class="n">sshKeyPaths</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;/persist/etc/ssh/ssh_host_ed25519_key&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="lanzaboote-secure-boot">Lanzaboote (Secure Boot)</h3>
<p>We will use Lanzaboote so we can enable Secure Boot.</p>
<blockquote>
<p>Secure Boot usually refers to a platform firmware capability to verify the boot components and ensure that only your own operating system is allowed to boot. - NixOS Wiki <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
</blockquote>
<p>Here are the relevant parts of my Nix config <code>modules/nixos/system/boot/default.nix</code> for Secure Boot.
We want to persist the /etc/secureboot folder, which is where our signing keys will be auto-generated by Lanzaboote
on our first boot. We also need to disable systemd-boot if we are using Lanzaboote.</p>
<p>The combination of auto-generate keys and auto-enroll means fewer manual steps we need to take post-install to enable
Secure Boot. However, the first time I did this, I did set this up manually following the Lanzaboote getting started guide.
Do whichever you prefer; it&rsquo;s not like it&rsquo;s many steps anyway.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">boot</span><span class="o">.</span><span class="n">lanzaboote</span> <span class="o">=</span> <span class="n">mkIf</span> <span class="n">cfg</span><span class="o">.</span><span class="n">secureBoot</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">pkiBundle</span> <span class="o">=</span> <span class="s2">&#34;/etc/secureboot&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">autoGenerateKeys</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">autoEnrollKeys</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">autoReboot</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">boot</span><span class="o">.</span><span class="n">systemd-boot</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">enable</span> <span class="o">=</span> <span class="no">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">configurationLimit</span> <span class="o">=</span> <span class="mi">20</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">editor</span> <span class="o">=</span> <span class="no">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">environment</span><span class="o">.</span><span class="n">persistence</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;/persist&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">directories</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;/etc/secureboot&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="impermanence">Impermanence</h3>
<p>My <a href="https://gitlab.com/hmajid2301/nixicle/-/blob/7ffe47fb27c804383d5c53405e918f7b4749bfaf/modules/nixos/system/impermanence/default.nix#L1">impermanence module</a> is longish,
so I won&rsquo;t go into lots of details. But we use btrfs to be able to roll back to a clean state, then copy files we specify
in our <code>persist</code> btrfs subvolume.</p>
<h4 id="disko">disko</h4>
<p>I use disko with nixos-anywhere to be able to partition my disks without lots of manual commands. Here is <a href="https://gitlab.com/hmajid2301/nixicle/-/blob/7ffe47fb27c804383d5c53405e918f7b4749bfaf/hosts/framework/disks.nix">my disko config</a>.
You can see the subvolumes we create for our btrfs setup, i.e., <code>/persist</code> for persisting files between reboots.</p>
<p>Here is where I define my LUKS settings as well, so that our data will be encrypted at rest (full-disk encryption).</p>
<h3 id="pcr-15">PCR 15</h3>
<details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">TPM</summary>
  
  <p>Take what I say here with a grain of salt; this is all based on my understanding.
This is from reading articles, and a bit of asking Claude to explain/draw stuff to make it more visual.</p>
<p>I have linked a few articles/videos that I found useful.</p>

</details>

<p>Eventually, we will want to enroll TPM to decrypt LUKS so that it is decrypted automatically but will only work with our
TPM chip soldered onto our motherboard. Part of this will be specifying the PCRs. I don&rsquo;t know loads about how it works.</p>
<p><a href="https://wiki.archlinux.org/title/Trusted_Platform_Module#Accessing_PCR_registers">PCR</a>, Platform Configuration Registers.</p>
<blockquote>
<p>Platform Configuration Registers (PCR) allow binding of the encryption of secrets to specific software versions and system state via hashes, so that the enrolled key is only accessible (may be &ldquo;unsealed&rdquo;) if specific trusted software and/or configuration is used. - Arch Wiki</p>
</blockquote>
<p>We check the previous registers before we give access to the data inside the TPM, i.e., our LUKS password stored inside
the TPM. There is a really good post <sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> about how TPM decryption might not be secure enough, though I don&rsquo;t think we need
to worry about PCR 9 <sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> (due to checksums on initrd, from what I understand anyway).</p>
<p>I may do a longer post about how this all works when I learn more myself. But if we don&rsquo;t check PCR 15, someone could
just copy the metadata and pretend they have our disk, assuming they have physical access to our PC. The TPM
is not checking the PCR 15 value, so therefore would just spit out our LUKS password, which they could then use
on our original disk. Now, of course, this all depends on your threat model, but I thought it&rsquo;d be fun to fix this issue,
at least on my laptop.</p>
<p>So I copied the <a href="https://forge.lel.lol/patrick/nix-config/src/commit/ab2cb2b4d554040ce208fc60624fe729a9d5e32b/modules/ensure-pcr.nix">module</a>.
Until the expected option is configured, it won&rsquo;t do anything; we won&rsquo;t know this until the system has been built.
So it&rsquo;ll be one more manual step we need to take, but perhaps worth it to make my system more secure.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">security</span><span class="o">.</span><span class="n">nixicle</span><span class="o">.</span><span class="n">pcr-verification</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># expectedPcr15 = &#34;caf33e79c645b65849256238a11fa68ae197e5cb89730c463c1cdf1d9128376f&#34;;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h2 id="steps">Steps</h2>
<p>Now onto how I deploy NixOS onto a new system/device with all the above playing nice and reducing the number of
manual steps we need to take. Normally, I had Claude create some scripts I can use with <code>go-task</code>, but for the sake
of this blog post, we will do it step by step, i.e., normally I would do <code>task install:secure</code>. Where we could then
follow the script using <a href="https://github.com/charmbracelet/gum">charm&rsquo;s gum</a> to make nice interactive scripts.
This includes info like the username, IP to SSH onto.</p>
<p>Anyway, make sure the device can be SSH&rsquo;d onto; usually you can just use a live media USB. I build my own with nixos-generators.
But any live media should be fine; use NixOS if you don&rsquo;t have anything set up. I do something like this:
<code>mkdir -p ~/.ssh &amp;&amp; curl https://github.com/hmajid2301.keys &gt; ~/.ssh/authorized_keys</code>, so that I can SSH onto the live
media. Also, take note of the IP address of the machine; you can find this by running <code>ip addr</code>.</p>
<p>For example, on my local network it might be <code>192.168.1.71</code>.</p>
<h3 id="install">Install</h3>
<p>Enter our encryption password to a file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">MY_PASS</span><span class="o">=</span><span class="k">$(</span>mktemp<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;YOUR_LUKS_PASSWORD&#34;</span> &gt; <span class="nv">$MY_PASS</span>
</span></span></code></pre></div><p>Get SSH keys ready for copying onto the new machine; this is so that sops can decrypt the various secrets. Either you
can copy the existing SSH keys and replace them after, or you can generate new ones and update your sops config and sops
files with these new keys. The latter is the more secure option, but I went with the first one as I was feeling lazy
(security through inconvenience is still security, right? Right?):</p>
<blockquote>
<p>Note: If this is your first installation and you don&rsquo;t have existing SSH keys at <code>/persist/etc/ssh/</code>, you can either generate temporary keys or skip this step and regenerate your secrets after installation with the new host keys that will be created.</p>
</blockquote>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">SSH_KEYS</span><span class="o">=</span><span class="k">$(</span>mktemp -d<span class="k">)</span>
</span></span><span class="line"><span class="cl">mkdir -p <span class="s2">&#34;</span><span class="nv">$SSH_KEYS</span><span class="s2">/persist/etc/ssh&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">sudo cp /persist/etc/ssh/ssh_host_ed25519_key <span class="s2">&#34;</span><span class="nv">$SSH_KEYS</span><span class="s2">/persist/etc/ssh/&#34;</span>
</span></span><span class="line"><span class="cl">sudo cp /persist/etc/ssh/ssh_host_ed25519_key.pub <span class="s2">&#34;</span><span class="nv">$SSH_KEYS</span><span class="s2">/persist/etc/ssh/&#34;</span>
</span></span><span class="line"><span class="cl">sudo cp /persist/etc/ssh/ssh_host_rsa_key <span class="s2">&#34;</span><span class="nv">$SSH_KEYS</span><span class="s2">/persist/etc/ssh/&#34;</span> 2&gt;/dev/null
</span></span><span class="line"><span class="cl">sudo cp /persist/etc/ssh/ssh_host_rsa_key.pub <span class="s2">&#34;</span><span class="nv">$SSH_KEYS</span><span class="s2">/persist/etc/ssh/&#34;</span> 2&gt;/dev/null
</span></span></code></pre></div><p>Update permissions on the SSH keys folder:</p>
<blockquote>
<p>Note: This step is crucial for sops-nix to work with impermanence. If you skip this, you&rsquo;ll need to re-encrypt your secrets with new host keys.</p>
</blockquote>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo chmod <span class="m">600</span> <span class="s2">&#34;</span><span class="nv">$SSH_KEYS</span><span class="s2">/persist/etc/ssh&#34;</span>/ssh_host_*_key 2&gt;/dev/null
</span></span><span class="line"><span class="cl">sudo chown -R <span class="k">$(</span>id -u<span class="k">)</span>:<span class="k">$(</span>id -g<span class="k">)</span> <span class="s2">&#34;</span><span class="nv">$SSH_KEYS</span><span class="s2">&#34;</span>
</span></span></code></pre></div><p>Then finally we can start the install process:</p>
<blockquote>
<p>Note: Update the username@host as needed, nixos is the default user for the NixOS ISO.</p>
</blockquote>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">nixos-anywhere <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>--flake <span class="s2">&#34;.#framework&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>--disk-encryption-keys /tmp/disk-encryption.key <span class="s2">&#34;</span><span class="nv">$MY_PASS</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>--extra-files <span class="s2">&#34;</span><span class="nv">$SSH_KEYS</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>--build-on-remote <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span><span class="s2">&#34;nixos@192.168.1.71&#34;</span>
</span></span></code></pre></div><h3 id="post-install">Post Install</h3>
<p>After the command is done, your PC will reboot. At this point, you can start to get it ready for Secure Boot if you want.
For a Framework laptop to enable Secure Boot, first we need to erase the Secure Boot settings.</p>
<p>It may differ on your device:</p>
<pre tabindex="0"><code>Framework-specific: Enter Setup Mode

On Framework you can enter the setup mode like this:

    Select &#34;Administer Secure Boot&#34;
    Select &#34;Erase all Secure Boot Settings&#34;

When you are done, press F10 to save and exit.
</code></pre><p>Then enter your password for LUKS so it can decrypt your drive and boot like normal. You will see your PC reboot
due to the auto-reboot we set above with Lanzaboote. That&rsquo;s fine; it&rsquo;s all very normal.
If your computer starts speaking Latin or emitting smoke, that&rsquo;s NOT normal. Please consult a priest or your local fire department.</p>
<p>On my Framework device, it then told me it&rsquo;s going to enroll my keys, and I didn&rsquo;t interrupt the process.
Then I logged into my device and enabled TPM decryption:</p>
<blockquote>
<p>Note: Replace <code>/dev/nvme0n1p2</code> with your actual LUKS partition. Check your disko config to confirm the correct partition path.</p>
</blockquote>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># The --tpm2-pcrs=15:sha256=0000... uses all zeros as a placeholder that will be updated after first boot</span>
</span></span><span class="line"><span class="cl">sudo systemd-cryptenroll /dev/nvme0n1p2 <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --wipe-slot<span class="o">=</span>tpm2 <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --tpm2-device<span class="o">=</span>auto <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --tpm2-pcrs<span class="o">=</span>0+2+7 <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --tpm2-pcrs<span class="o">=</span>15:sha256<span class="o">=</span><span class="m">0000000000000000000000000000000000000000000000000000000000000000</span>
</span></span></code></pre></div><p>Check and make sure it all looks normal; you should see files signed (except perhaps one old one):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ sudo -E sbctl verify
</span></span><span class="line"><span class="cl"><span class="o">[</span>sudo<span class="o">]</span> password <span class="k">for</span> haseeb:
</span></span><span class="line"><span class="cl">Verifying file database and EFI images in /boot...
</span></span><span class="line"><span class="cl">✓ /boot/EFI/BOOT/BOOTX64.EFI is signed
</span></span><span class="line"><span class="cl">✓ /boot/EFI/Linux/nixos-generation-40-7ik2vgrngo25ml7c2wb43uy52pt7ahpovfrrxczp445y76u56dlq.efi is signed
</span></span><span class="line"><span class="cl">✗ /boot/EFI/nixos/kernel-6.18.2-otn6nn3tkudhh5xpj5736u2q3h4kjzojd6fkg4rqzdf5l5c7gxuq.efi is not signed
</span></span><span class="line"><span class="cl">✓ /boot/EFI/systemd/systemd-bootx64.efi is signed
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Verify Secure Boot is working</span>
</span></span><span class="line"><span class="cl">sudo bootctl status
</span></span></code></pre></div><p>Now we can enable Secure Boot; again, this may vary on your device.</p>
<pre tabindex="0"><code>On Framework you need to manually enable Secure Boot:

    Select &#34;Administer Secure Boot&#34;
    Enable &#34;Enforce Secure Boot&#34;

When you are done, press F10 to save and exit.
</code></pre><p>Finally, after logging in again, we can run the following to get the actual PCR 15 hash value:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ systemd-analyze pcrs <span class="m">15</span> --json<span class="o">=</span>short
</span></span><span class="line"><span class="cl"><span class="o">[{</span><span class="s2">&#34;nr&#34;</span>:15,<span class="s2">&#34;name&#34;</span>:<span class="s2">&#34;system-identity&#34;</span>,<span class="s2">&#34;sha256&#34;</span>:<span class="s2">&#34;0000000000000000000000000000000000000000000000000000000000000000&#34;</span><span class="o">}]</span>
</span></span></code></pre></div><p>Take the sha256 value from this output and update your config&rsquo;s <code>security.nixicle.pcr-verification.expectedPcr15</code> field with the actual hash, then run:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo nixos-rebuild switch --flake .#framework
</span></span></code></pre></div><p>Then, of course, go about setting up everything else. If you copied existing SSH keys during installation,
you may want to rotate them for better security by generating new host keys and re-encrypting your sops secrets with the new keys.</p>
<p>That&rsquo;s it! Hopefully, you found that useful! I appreciate it was very long-winded, but that&rsquo;s how I was able to do a mostly
unattended install with few manual steps after getting the process started.
If you made it this far, you deserve a cookie 🍪 (or at least a functioning NixOS installation).</p>
<h2 id="appendix">Appendix</h2>
<p>I had to do a bunch of research whilst working on this; here are some useful links I used:</p>
<ul>
<li>A really interesting post about how you can break TPM encryption with Secure Boot: <a href="https://oddlama.org/blog/bypassing-disk-encryption-with-tpm2-unlock/#crude-implementation-of-pcr15-verification">https://oddlama.org/blog/bypassing-disk-encryption-with-tpm2-unlock/#crude-implementation-of-pcr15-verification</a></li>
<li>Lanzaboote getting started guide: <a href="https://github.com/nix-community/lanzaboote/blob/master/docs/getting-started/enable-secure-boot.md">https://github.com/nix-community/lanzaboote/blob/master/docs/getting-started/enable-secure-boot.md</a></li>
<li>Idea for copying SSH keys: <a href="https://github.com/nix-community/nixos-anywhere/issues/604">https://github.com/nix-community/nixos-anywhere/issues/604</a></li>
<li>nixos-anywhere SSH keys copy: <a href="https://github.com/nix-community/nixos-anywhere/blob/main/docs/howtos/secrets.md#example-decrypting-an-openssh-host-key-with-pass">https://github.com/nix-community/nixos-anywhere/blob/main/docs/howtos/secrets.md#example-decrypting-an-openssh-host-key-with-pass</a></li>
<li>Discourse post with same issue as mine: <a href="https://discourse.nixos.org/t/impermanence-sops-nix-nixos-anywhere-lead-to-missing-hashedpasswordfile-s/66472">https://discourse.nixos.org/t/impermanence-sops-nix-nixos-anywhere-lead-to-missing-hashedpasswordfile-s/66472</a></li>
<li>Another discourse post with a similar issue: <a href="https://discourse.nixos.org/t/nixos-anywhere-failing-to-deploy-secrets/68392">https://discourse.nixos.org/t/nixos-anywhere-failing-to-deploy-secrets/68392</a></li>
</ul>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://ryanseipp.com/posts/nixos-automated-deployment/">https://ryanseipp.com/posts/nixos-automated-deployment/</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://wiki.nixos.org/wiki/Secure_Boot">https://wiki.nixos.org/wiki/Secure_Boot</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p><a href="https://oddlama.org/blog/bypassing-disk-encryption-with-tpm2-unlock/#crude-implementation-of-pcr15-verification">https://oddlama.org/blog/bypassing-disk-encryption-with-tpm2-unlock/#crude-implementation-of-pcr15-verification</a>&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p><a href="https://www.youtube.com/watch?v=hXblxnDS6eU">https://www.youtube.com/watch?v=hXblxnDS6eU</a>&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL - How to Fix Tailscale Using Mullvad as Exit Nodes on NixOS</title>
      <link>https://haseebmajid.dev/posts/2025-12-29-til-how-to-fix-tailscale-using-mullvad-as-exit-nodes-on-nixos/</link>
      <pubDate>Mon, 29 Dec 2025 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2025-12-29-til-how-to-fix-tailscale-using-mullvad-as-exit-nodes-on-nixos/</guid>
      <description>&lt;p&gt;On NixOS when ever I enabled &lt;a href=&#34;https://tailscale.com/kb/1258/mullvad-exit-nodes&#34;&gt;mullvad&lt;/a&gt; as exit nodes via tailscale (the &lt;a href=&#34;https://github.com/DeedleFake/trayscale&#34;&gt;trayscale app&lt;/a&gt;).
My internet would stop working, which was weird as this worked fine on my other devices i.e. Ubuntu or my phone.&lt;/p&gt;
&lt;p&gt;Well turns out it seems to be the way NixOS works with the firewall, you can read all the details here &lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. Where
the poster explains it really well.&lt;/p&gt;
&lt;p&gt;My understanding is the following:&lt;/p&gt;
&lt;p&gt;This changes RPF (reverse path filtering) from &amp;ldquo;strict&amp;rdquo; to &amp;ldquo;loose&amp;rdquo; mode:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>On NixOS when ever I enabled <a href="https://tailscale.com/kb/1258/mullvad-exit-nodes">mullvad</a> as exit nodes via tailscale (the <a href="https://github.com/DeedleFake/trayscale">trayscale app</a>).
My internet would stop working, which was weird as this worked fine on my other devices i.e. Ubuntu or my phone.</p>
<p>Well turns out it seems to be the way NixOS works with the firewall, you can read all the details here <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>. Where
the poster explains it really well.</p>
<p>My understanding is the following:</p>
<p>This changes RPF (reverse path filtering) from &ldquo;strict&rdquo; to &ldquo;loose&rdquo; mode:</p>
<ul>
<li>Strict: Reply must go back out the exact same interface it came in on</li>
<li>Loose: Reply can go out any interface, as long as there&rsquo;s a valid route back to the source</li>
</ul>
<h2 id="solution">Solution</h2>
<p>But the fix is enabling this option: <code>networking.firewall.checkReversePath = &quot;loose&quot;;</code>. Please make sure you understand
exactly what you are doing when enabling this.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://github.com/tailscale/tailscale/issues/4432#issuecomment-1112819111">https://github.com/tailscale/tailscale/issues/4432#issuecomment-1112819111</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL - How to Fix App Lounge Updates Getting Stuck (MurenaOS/eOS)</title>
      <link>https://haseebmajid.dev/posts/2025-12-28-til-how-to-fix-app-lounge-updates-getting-stuck-murenaos-eos-/</link>
      <pubDate>Sun, 28 Dec 2025 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2025-12-28-til-how-to-fix-app-lounge-updates-getting-stuck-murenaos-eos-/</guid>
      <description>&lt;p&gt;If you use a FairPhone, or a phone running &lt;a href=&#34;https://e.foundation/e-os/&#34;&gt;eOS/MurenaOS&lt;/a&gt;, which is as degoogled version of
Android (no affiliation to me besides I use their devices/OS).&lt;/p&gt;
&lt;p&gt;Sometimes the app store &amp;ldquo;App Lounge&amp;rdquo; gets stuck updating apps, my old solution was to delete the app and reinstall it
but that can be a pain. Then I discovered a slightly better solution.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Go to settings&lt;/li&gt;
&lt;li&gt;Apps &amp;amp; Notifications&lt;/li&gt;
&lt;li&gt;Find App Lounge&lt;/li&gt;
&lt;li&gt;Then go to Storage &amp;amp; cache&lt;/li&gt;
&lt;li&gt;Clear storage and &amp;ldquo;Delete app data&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You will need to log in again to the app, but the update process shouldn&amp;rsquo;t get stuck anymore hopefully.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>If you use a FairPhone, or a phone running <a href="https://e.foundation/e-os/">eOS/MurenaOS</a>, which is as degoogled version of
Android (no affiliation to me besides I use their devices/OS).</p>
<p>Sometimes the app store &ldquo;App Lounge&rdquo; gets stuck updating apps, my old solution was to delete the app and reinstall it
but that can be a pain. Then I discovered a slightly better solution.</p>
<ul>
<li>Go to settings</li>
<li>Apps &amp; Notifications</li>
<li>Find App Lounge</li>
<li>Then go to Storage &amp; cache</li>
<li>Clear storage and &ldquo;Delete app data&rdquo;</li>
</ul>
<p>You will need to log in again to the app, but the update process shouldn&rsquo;t get stuck anymore hopefully.</p>
<p>[^1] <a href="https://community.e.foundation/t/applounge-updates-stuck/47296/11">https://community.e.foundation/t/applounge-updates-stuck/47296/11</a></p>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Fix PAM Issues With Home Manager on Non-NixOS Setups</title>
      <link>https://haseebmajid.dev/posts/2025-12-12-how-to-fix-pam-issues-with-home-manager-on-non-nixos-setups/</link>
      <pubDate>Fri, 12 Dec 2025 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2025-12-12-how-to-fix-pam-issues-with-home-manager-on-non-nixos-setups/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
The pam_shim is POC code, so be careful with using it on your own machine. Use it at your own risk.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;the-problem&#34;&gt;The problem&lt;/h2&gt;
&lt;p&gt;At work I have to use Ubuntu but I want to share my nix config between all my devices i.e. desktop and work laptop.
On my home setup I have been using Niri with Noctalia shell (quickshell) on NixOS. Everything works fine I can use
the Noctalia shell lock screen and can authenticate fine.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<blockquote>
<p>[!WARNING]
The pam_shim is POC code, so be careful with using it on your own machine. Use it at your own risk.</p>
</blockquote>
<h2 id="the-problem">The problem</h2>
<p>At work I have to use Ubuntu but I want to share my nix config between all my devices i.e. desktop and work laptop.
On my home setup I have been using Niri with Noctalia shell (quickshell) on NixOS. Everything works fine I can use
the Noctalia shell lock screen and can authenticate fine.</p>
<p>However when I tried to do that on my Ubuntu machine, it wouldn&rsquo;t let me log back in after locking the screen.
Remember everything is installed via Nix (home-manager).</p>
<h3 id="pam">PAM</h3>
<p>PAM is Linux&rsquo;s standard authentication framework. When you lock your screen and type your password, PAM handles the
authentication logic. It&rsquo;s called &ldquo;pluggable&rdquo; because system administrators can configure different authentication
methods (passwords, fingerprints, smart cards) without modifying applications.</p>
<h3 id="pam--nix">PAM &amp; Nix</h3>
<p>Nix packages are completely self-contained. When you install a package with Nix, it includes all its dependencies in <code>/nix/store/</code>.
But this creates a problem with PAM:</p>
<p>NixOS</p>
<pre tabindex="0"><code>Application → /nix/store/xxx-linux-pam/lib/libpam.so.2
           → /nix/store/yyy-pam-modules/lib/security/pam_unix.so
           → /etc/pam.d/ (managed by NixOS)
           → Authenticate ✅
</code></pre><p>Ubuntu</p>
<pre tabindex="0"><code>Application → /nix/store/xxx-linux-pam/lib/libpam.so.2
           → /nix/store/yyy-pam-modules/lib/security/pam_unix.so (doesn&#39;t exist!)
           → /etc/pam.d/ (managed by Ubuntu, expects different paths)
           → Authentication FAILS ❌
</code></pre><p>So we are locked out &ldquo;forever&rdquo; until we reboot, obviously not great. I know I hit this issue before with Hyprland
and hyprlock (or swaylock) and ended up installing them via apt and not Nix. But this time I wanted to avoid
doing that. I came across this <a href="https://github.com/nix-community/home-manager/issues/7027">issue</a>.</p>
<h2 id="solution">Solution</h2>
<p>We can use <code>pam_shim</code>, which creates a translation layer we can use. Whilst not breaking Nix&rsquo;s package isolation.</p>
<ol>
<li>
<p><strong>PAM Shim Library</strong>: <code>pam-shim</code> creates a wrapper library that intercepts PAM calls and redirects them to the host system&rsquo;s PAM library (<code>/lib/x86_64-linux-gnu/libpam.so.0</code> on Ubuntu)</p>
</li>
<li>
<p><strong>Patching</strong>: The <code>replacePam</code> function uses <code>patchelf</code> to replace PAM library references in the QuickShell binary with the shim library</p>
</li>
<li>
<p><strong>Runtime</strong>: When noctalia-shell&rsquo;s lock screen tries to authenticate:</p>
<ul>
<li>QuickShell calls PAM functions</li>
<li>Calls go through the shim library</li>
<li>Shim redirects to Ubuntu&rsquo;s native PAM</li>
<li>Authentication works against Ubuntu&rsquo;s PAM stack</li>
</ul>
</li>
</ol>
<pre tabindex="0"><code>QuickShell → pam_shim intercepts pam_authenticate()
        → fork() subprocess
        → exec /lib64/ld-linux-x86-64.so.2 (system dynamic linker)
        → loads /lib/x86_64-linux-gnu/libpam.so.0 (Ubuntu&#39;s native PAM)
        → reads /etc/pam.d/common-auth (Ubuntu&#39;s PAM config)
        → loads /lib/x86_64-linux-gnu/security/pam_unix.so (Ubuntu&#39;s PAM module)
        → checks /etc/shadow (system password database)
        → returns PAM_SUCCESS via IPC to pam_shim
        → pam_shim returns result to QuickShell
        → Authentication SUCCEEDS ✅
</code></pre><h2 id="fix">Fix</h2>
<p>update <code>flake.nix</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="c1"># PAM shim for non-NixOS systems</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Using &#39;next&#39; branch for full libpam.so.0 API coverage</span>
</span></span><span class="line"><span class="cl"><span class="n">pam-shim</span> <span class="err">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;github:Cu3PO42/pam_shim/next&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">inputs</span><span class="o">.</span><span class="n">nixpkgs</span><span class="o">.</span><span class="n">follows</span> <span class="o">=</span> <span class="s2">&#34;nixpkgs&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="n">inputs</span><span class="o">.</span><span class="n">pam-shim</span><span class="o">.</span><span class="n">homeModules</span><span class="o">.</span><span class="n">default</span>
</span></span></code></pre></div><p>In our non-NixOS system i.e. <code>modules/roles/non-nixos.nix</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="c1"># PAM authentication fix for non-NixOS</span>
</span></span><span class="line"><span class="cl"><span class="n">pamShim</span><span class="o">.</span><span class="n">enable</span> <span class="err">=</span> <span class="no">true</span><span class="p">;</span>
</span></span></code></pre></div><p>And add this overlay for all quickshell packages (what Noctalia shell uses). This will also work with other quickshells
like DankMaterialLinux.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="c1"># Override quickshell globally with PAM-shimmed version</span>
</span></span><span class="line"><span class="cl"><span class="n">nixpkgs</span><span class="o">.</span><span class="n">overlays</span> <span class="err">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">  <span class="p">(</span><span class="n">final</span><span class="p">:</span> <span class="n">prev</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">quickshell</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">lib</span><span class="o">.</span><span class="n">pamShim</span><span class="o">.</span><span class="n">replacePam</span> <span class="n">prev</span><span class="o">.</span><span class="n">quickshell</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="p">];</span>
</span></span></code></pre></div><p>Where you have your noctalia-shell Nix config defined, i.e. <code>noctalia.nix</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="c1"># Noctalia shell with PAM shim for lock screen authentication</span>
</span></span><span class="line"><span class="cl"><span class="n">systemd</span><span class="o">.</span><span class="n">user</span><span class="o">.</span><span class="n">services</span><span class="o">.</span><span class="n">noctalia-shell</span> <span class="err">=</span> <span class="n">mkIf</span> <span class="n">config</span><span class="o">.</span><span class="n">desktops</span><span class="o">.</span><span class="n">addons</span><span class="o">.</span><span class="n">noctalia</span><span class="o">.</span><span class="n">enable</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="k">let</span>
</span></span><span class="line"><span class="cl">    <span class="n">shimmedQuickshell</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">lib</span><span class="o">.</span><span class="n">pamShim</span><span class="o">.</span><span class="n">replacePam</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">quickshell</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">in</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">Service</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">ExecStart</span> <span class="o">=</span> <span class="n">lib</span><span class="o">.</span><span class="n">mkForce</span> <span class="s2">&#34;</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">writeShellScript</span> <span class="s2">&#34;noctalia-nixgl&#34;</span> <span class="s1">&#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">        export PATH=&#34;</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">wlsunset</span><span class="si">}</span><span class="s1">/bin:</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">wl-clipboard</span><span class="si">}</span><span class="s1">/bin:</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">cliphist</span><span class="si">}</span><span class="s1">/bin:</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">coreutils</span><span class="si">}</span><span class="s1">/bin:</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">gnugrep</span><span class="si">}</span><span class="s1">/bin:</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">gnused</span><span class="si">}</span><span class="s1">/bin:</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">bash</span><span class="si">}</span><span class="s1">/bin:/run/wrappers/bin:</span><span class="si">${</span><span class="n">config</span><span class="o">.</span><span class="n">home</span><span class="o">.</span><span class="n">profileDirectory</span><span class="si">}</span><span class="s1">/bin:/usr/bin:/bin&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">        exec </span><span class="si">${</span><span class="n">config</span><span class="o">.</span><span class="n">lib</span><span class="o">.</span><span class="n">nixGL</span><span class="o">.</span><span class="n">wrap</span> <span class="n">shimmedQuickshell</span><span class="si">}</span><span class="s1">/bin/quickshell -p </span><span class="si">${</span><span class="n">config</span><span class="o">.</span><span class="n">programs</span><span class="o">.</span><span class="n">noctalia-shell</span><span class="o">.</span><span class="n">package</span><span class="si">}</span><span class="s1">/share/noctalia-shell
</span></span></span><span class="line"><span class="cl"><span class="s1">      &#39;&#39;</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span></code></pre></div><h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://github.com/Cu3PO42/pam_shim">pam_shim Repository</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL - How to Enable Video Thumbnails for SMB Share on Nixos and Nautilus</title>
      <link>https://haseebmajid.dev/posts/2025-11-06-til-how-to-enable-video-thumbnails-for-smb-share-on-nixos/</link>
      <pubDate>Thu, 06 Nov 2025 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2025-11-06-til-how-to-enable-video-thumbnails-for-smb-share-on-nixos/</guid>
      <description>&lt;p&gt;So recently I have setup a mini NAS and I connect to it via an SMB share, but on nautilus I noticed that
unlike my local files for the videos it would not create thumbnails when I was using nautilus (file manager for Gnome).&lt;/p&gt;
&lt;p&gt;The fix ended up being pretty simple &lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Nautilus &amp;gt; Preference &amp;gt; Show thumbnails.

Set it to &amp;#34;On this computer only&amp;#34; for only seeing thumbnails on your system, .

If u need it to do same for a remote server, select &amp;#34;All files&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Or since we are managing this via NixOS and want to do things declaratively we can do do the following in our Nix config &lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>So recently I have setup a mini NAS and I connect to it via an SMB share, but on nautilus I noticed that
unlike my local files for the videos it would not create thumbnails when I was using nautilus (file manager for Gnome).</p>
<p>The fix ended up being pretty simple <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>:</p>
<pre tabindex="0"><code>Nautilus &gt; Preference &gt; Show thumbnails.

Set it to &#34;On this computer only&#34; for only seeing thumbnails on your system, .

If u need it to do same for a remote server, select &#34;All files&#34;
</code></pre><p>Or since we are managing this via NixOS and want to do things declaratively we can do do the following in our Nix config <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="n">dconf</span><span class="o">.</span><span class="n">settings</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;org/gnome/nautilus/preferences&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">show-image-thumbnails</span> <span class="o">=</span> <span class="s2">&#34;always&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>That&rsquo;s it, in your SMB share you should now be able to view thumbnails for all of your videos.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://www.reddit.com/r/gnome/comments/u6t0u8/comment/i5aq8no/?utm_source=share&amp;utm_medium=web3x&amp;utm_name=web3xcss&amp;utm_term=1&amp;utm_content=share_button">https://www.reddit.com/r/gnome/comments/u6t0u8/comment/i5aq8no/?utm_source=share&amp;utm_medium=web3x&amp;utm_name=web3xcss&amp;utm_term=1&amp;utm_content=share_button</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://gitlab.com/hmajid2301/nixicle/-/blob/changes/modules/nixos/roles/desktop/addons/nautilus/default.nix">https://gitlab.com/hmajid2301/nixicle/-/blob/changes/modules/nixos/roles/desktop/addons/nautilus/default.nix</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>Yak Shaving: Neotest Edition</title>
      <link>https://haseebmajid.dev/posts/2025-11-02-yak-shaving-neotest-edition/</link>
      <pubDate>Sun, 02 Nov 2025 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2025-11-02-yak-shaving-neotest-edition/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; The fix is in this commit: &lt;a href=&#34;https://gitlab.com/hmajid2301/nixicle/-/commit/cfababc9e3a1dfdd1917d0b87cb17fcdd655bfdc&#34;&gt;https://gitlab.com/hmajid2301/nixicle/-/commit/cfababc9e3a1dfdd1917d0b87cb17fcdd655bfdc&lt;/a&gt;  Fixing stuff is fun! 🥳&lt;/p&gt;
&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;p&gt;I use Neovim (btw!) and Nix (btw btw!!!). I mean, half the reason to use these tools is to tell other people you use them, right? 😉 Otherwise, why would I go through the pain I experienced today? &amp;ldquo;What pain?&amp;rdquo; you ask. Good question!&lt;/p&gt;
&lt;p&gt;A few weeks ago, I noticed that running tests in Neovim was failing with a &lt;code&gt;no test found&lt;/code&gt; error. I&amp;rsquo;m using &lt;code&gt;neotest&lt;/code&gt; and &lt;code&gt;neotest-golang&lt;/code&gt; to run tests within Neovim.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TL;DR:</strong> The fix is in this commit: <a href="https://gitlab.com/hmajid2301/nixicle/-/commit/cfababc9e3a1dfdd1917d0b87cb17fcdd655bfdc">https://gitlab.com/hmajid2301/nixicle/-/commit/cfababc9e3a1dfdd1917d0b87cb17fcdd655bfdc</a>  Fixing stuff is fun! 🥳</p>
<h2 id="background">Background</h2>
<p>I use Neovim (btw!) and Nix (btw btw!!!). I mean, half the reason to use these tools is to tell other people you use them, right? 😉 Otherwise, why would I go through the pain I experienced today? &ldquo;What pain?&rdquo; you ask. Good question!</p>
<p>A few weeks ago, I noticed that running tests in Neovim was failing with a <code>no test found</code> error. I&rsquo;m using <code>neotest</code> and <code>neotest-golang</code> to run tests within Neovim.</p>
<p>Here&rsquo;s a <a href="https://gitlab.com/hmajid2301/nixicle/-/blob/main/modules/home/cli/editors/neovim/lua/myLuaConf/test/init.lua">link to my config</a>.</p>
<p>I&rsquo;m also using the nightly build of Neovim, so getting the latest version isn&rsquo;t great if you want stability. Things can break at any moment, like running tests. Anyway, today (November 1st), I had some free time and decided to spend most of the day fixing this issue.  poświęcenie</p>
<h2 id="setup">Setup</h2>
<p>I&rsquo;m using NixOS (should I do another &ldquo;btw&rdquo; joke??? 🤔), so I want to manage all my dependencies with Nix. I&rsquo;m also using NixCats, which allows me to configure Neovim with Lua but package it with Nix (it has a few other features I don&rsquo;t really use, but others might).</p>
<p>One of the nice features of NixCats is that we can easily add Neovim plugins that aren&rsquo;t in <a href="https://search.nixos.org/packages?type=packages&amp;query=vimPLugins&amp;channel=unstable"><code>nixpkgs</code></a> using our flake inputs:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="n">plugins-neotest-golang</span> <span class="err">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;github:fredrikaverpil/neotest-golang&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">flake</span> <span class="o">=</span> <span class="no">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>We just need to add <code>plugins-x</code>, and then we can reference it in our NixCats config like <code>pkgs.neovimPlugins.neotest-golang</code>. It&rsquo;s tied to my flake, so I can just run <code>nix flake update</code> to update those Neovim plugins along with every other input. Then I upgrade my setup, as you might with <code>apt upgrade</code>, but with <code>nh os switch</code> or <code>nh home switch</code>.</p>
<h2 id="debugging-">Debugging 🐛</h2>
<p>This setup means I can use the latest version of this plugin (v2.5.0 at the time of writing), but even upgrading to the latest version wasn&rsquo;t enough. I probably shouldn&rsquo;t have tied it to the main branch. 😅</p>
<p>I found this <a href="https://github.com/fredrikaverpil/neotest-golang/issues/386">issue</a>, which made me think I should try the latest version.</p>
<p>After a lot of (wasted) debugging, I finally read the <a href="https://fredrikaverpil.github.io/neotest-golang/install/">migration guide</a>. An amateur mistake, I know. 🤦</p>
<h3 id="nvim-treesitter">nvim-treesitter</h3>
<p>It turns out I need to use <code>nvim-treesitter</code>, but the <code>main</code> branch, not the <code>master</code> branch, which is frozen and no longer under development. There&rsquo;s an <a href="https://github.com/iofq/nvim-treesitter-main">existing overlay</a> we can use so we don&rsquo;t have to change how we install our dependencies.</p>
<p>Like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="n">nvim-treesitter-main</span> <span class="err">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;github:iofq/nvim-treesitter-main&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">inputs</span><span class="o">.</span><span class="n">nixpkgs</span><span class="o">.</span><span class="n">follows</span> <span class="o">=</span> <span class="s2">&#34;nixpkgs&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>I&rsquo;m also using <code>snowfall-lib</code>, so I can add a new overlay like so in my <code>flake.nix</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="n">overlays</span> <span class="err">=</span> <span class="k">with</span> <span class="n">inputs</span><span class="p">;</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">    <span class="n">nvim-treesitter-main</span><span class="o">.</span><span class="n">overlays</span><span class="o">.</span><span class="n">default</span>
</span></span><span class="line"><span class="cl"><span class="p">];</span>
</span></span></code></pre></div><p>I also created a new overlay file, <code>overlays/nvim-treesitter-main/default.nix</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="n">inputs</span><span class="p">:</span> <span class="n">final</span><span class="p">:</span> <span class="n">prev</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">vimPlugins</span> <span class="o">=</span> <span class="n">prev</span><span class="o">.</span><span class="n">vimPlugins</span> <span class="o">//</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">nvim-treesitter</span> <span class="o">=</span> <span class="n">prev</span><span class="o">.</span><span class="n">vimPlugins</span><span class="o">.</span><span class="n">nvim-treesitter</span><span class="o">.</span><span class="n">withAllGrammars</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">nvim-treesitter-textobjects</span> <span class="o">=</span> <span class="n">prev</span><span class="o">.</span><span class="n">vimPlugins</span><span class="o">.</span><span class="n">nvim-treesitter-textobjects</span><span class="o">.</span><span class="n">overrideAttrs</span> <span class="p">(</span><span class="n">old</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">dependencies</span> <span class="o">=</span> <span class="k">with</span> <span class="n">prev</span><span class="o">.</span><span class="n">vimPlugins</span><span class="p">;</span> <span class="p">[</span> <span class="n">nvim-treesitter</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>You can also add the cache to save some time rebuilding all the grammars on your own machine:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="n">trusted-substituters</span> <span class="err">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;https://nvim-treesitter-main.cachix.org&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">];</span>
</span></span></code></pre></div><p>Then, in my Neovim config, I updated the setup to look like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="n">require</span><span class="p">(</span><span class="s2">&#34;nvim-treesitter&#34;</span><span class="p">).</span><span class="n">setup</span><span class="p">()</span>
</span></span></code></pre></div><p>After this, when I opened a Markdown document, I noticed it was all white. But using <code>InspectTree</code>, the AST looked correct. It seemed like highlighting wasn&rsquo;t working, which, again, if I had read the docs, I would&rsquo;ve seen that I need to create an autocommand.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="n">vim.api</span><span class="p">.</span><span class="n">nvim_create_autocmd</span><span class="p">(</span><span class="s2">&#34;FileType&#34;</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="n">pattern</span> <span class="o">=</span> <span class="s2">&#34;*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="n">callback</span> <span class="o">=</span> <span class="kr">function</span><span class="p">(</span><span class="n">args</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="c1">-- Add error handling to prevent crashes during session restore</span>
</span></span><span class="line"><span class="cl">		<span class="kd">local</span> <span class="n">success</span><span class="p">,</span> <span class="n">err</span> <span class="o">=</span> <span class="n">pcall</span><span class="p">(</span><span class="kr">function</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">			<span class="c1">-- Check if parser is available before starting</span>
</span></span><span class="line"><span class="cl">			<span class="kd">local</span> <span class="n">lang</span> <span class="o">=</span> <span class="n">vim.bo</span><span class="p">[</span><span class="n">args.buf</span><span class="p">].</span><span class="n">filetype</span>
</span></span><span class="line"><span class="cl">			<span class="kd">local</span> <span class="n">ts_lang</span> <span class="o">=</span> <span class="n">vim.treesitter</span><span class="p">.</span><span class="n">language.get_lang</span><span class="p">(</span><span class="n">lang</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">			<span class="kr">if</span> <span class="n">ts_lang</span> <span class="kr">then</span>
</span></span><span class="line"><span class="cl">				<span class="n">vim.treesitter</span><span class="p">.</span><span class="n">start</span><span class="p">(</span><span class="n">args.buf</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">			<span class="kr">end</span>
</span></span><span class="line"><span class="cl">		<span class="kr">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="kr">if</span> <span class="ow">not</span> <span class="n">success</span> <span class="kr">then</span>
</span></span><span class="line"><span class="cl">			<span class="c1">-- Silently fail if treesitter can&#39;t start for this buffer</span>
</span></span><span class="line"><span class="cl">			<span class="n">vim.notify</span><span class="p">(</span><span class="s2">&#34;Treesitter failed to start for &#34;</span> <span class="o">..</span> <span class="n">vim.bo</span><span class="p">[</span><span class="n">args.buf</span><span class="p">].</span><span class="n">filetype</span> <span class="o">..</span> <span class="s2">&#34;: &#34;</span> <span class="o">..</span> <span class="n">err</span><span class="p">,</span> <span class="n">vim.log</span><span class="p">.</span><span class="n">levels.DEBUG</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="kr">end</span>
</span></span><span class="line"><span class="cl">	<span class="kr">end</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span></code></pre></div><h4 id="textobjects">textobjects</h4>
<p>To set up textobjects, we need to set up the plugin separately as well, also using the <code>main</code> branch. It now looks something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="n">require</span><span class="p">(</span><span class="s2">&#34;nvim-treesitter-textobjects&#34;</span><span class="p">).</span><span class="n">setup</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="n">select</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">lookahead</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">selection_modes</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="p">[</span><span class="s2">&#34;@parameter.outer&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;v&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="p">[</span><span class="s2">&#34;@function.outer&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;V&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="p">[</span><span class="s2">&#34;@class.outer&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;&lt;c-v&gt;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="n">include_surrounding_whitespace</span> <span class="o">=</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="n">move</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">set_jumps</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">-- Set up select keymaps using the new API</span>
</span></span><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">({</span> <span class="s2">&#34;x&#34;</span><span class="p">,</span> <span class="s2">&#34;o&#34;</span> <span class="p">},</span> <span class="s2">&#34;af&#34;</span><span class="p">,</span> <span class="kr">function</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">require</span><span class="p">(</span><span class="s2">&#34;nvim-treesitter-textobjects.select&#34;</span><span class="p">).</span><span class="n">select_textobject</span><span class="p">(</span><span class="s2">&#34;@function.outer&#34;</span><span class="p">,</span> <span class="s2">&#34;textobjects&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">({</span> <span class="s2">&#34;x&#34;</span><span class="p">,</span> <span class="s2">&#34;o&#34;</span> <span class="p">},</span> <span class="s2">&#34;if&#34;</span><span class="p">,</span> <span class="kr">function</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">require</span><span class="p">(</span><span class="s2">&#34;nvim-treesitter-textobjects.select&#34;</span><span class="p">).</span><span class="n">select_textobject</span><span class="p">(</span><span class="s2">&#34;@function.inner&#34;</span><span class="p">,</span> <span class="s2">&#34;textobjects&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">({</span> <span class="s2">&#34;x&#34;</span><span class="p">,</span> <span class="s2">&#34;o&#34;</span> <span class="p">},</span> <span class="s2">&#34;ac&#34;</span><span class="p">,</span> <span class="kr">function</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">require</span><span class="p">(</span><span class="s2">&#34;nvim-treesitter-textobjects.select&#34;</span><span class="p">).</span><span class="n">select_textobject</span><span class="p">(</span><span class="s2">&#34;@class.outer&#34;</span><span class="p">,</span> <span class="s2">&#34;textobjects&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span><span class="p">)</span>
</span></span></code></pre></div><p>Notice how we now set up the textobjects as keybindings. Then, for <code>swap</code> and <code>repeatable</code>, assuming you were using those, it looks something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;leader&gt;a&#34;</span><span class="p">,</span> <span class="kr">function</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">require</span><span class="p">(</span><span class="s2">&#34;nvim-treesitter-textobjects.swap&#34;</span><span class="p">).</span><span class="n">swap_next</span><span class="p">(</span><span class="s2">&#34;@parameter.inner&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;leader&gt;A&#34;</span><span class="p">,</span> <span class="kr">function</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">require</span><span class="p">(</span><span class="s2">&#34;nvim-treesitter-textobjects.swap&#34;</span><span class="p">).</span><span class="n">swap_previous</span><span class="p">(</span><span class="s2">&#34;@parameter.outer&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">local</span> <span class="n">ts_repeat_ok</span><span class="p">,</span> <span class="n">ts_repeat_move</span> <span class="o">=</span> <span class="n">pcall</span><span class="p">(</span><span class="n">require</span><span class="p">,</span> <span class="s2">&#34;nvim-treesitter-textobjects.repeatable_move&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="kr">if</span> <span class="n">ts_repeat_ok</span> <span class="kr">then</span>
</span></span><span class="line"><span class="cl">    <span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">({</span> <span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;x&#34;</span><span class="p">,</span> <span class="s2">&#34;o&#34;</span> <span class="p">},</span> <span class="s2">&#34;;&#34;</span><span class="p">,</span> <span class="n">ts_repeat_move.repeat_last_move_next</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">({</span> <span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;x&#34;</span><span class="p">,</span> <span class="s2">&#34;o&#34;</span> <span class="p">},</span> <span class="s2">&#34;,&#34;</span><span class="p">,</span> <span class="n">ts_repeat_move.repeat_last_move_previous</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">({</span> <span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;x&#34;</span><span class="p">,</span> <span class="s2">&#34;o&#34;</span> <span class="p">},</span> <span class="s2">&#34;f&#34;</span><span class="p">,</span> <span class="n">ts_repeat_move.builtin_f_expr</span><span class="p">,</span> <span class="p">{</span> <span class="n">expr</span> <span class="o">=</span> <span class="kc">true</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">({</span> <span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;x&#34;</span><span class="p">,</span> <span class="s2">&#34;o&#34;</span> <span class="p">},</span> <span class="s2">&#34;F&#34;</span><span class="p">,</span> <span class="n">ts_repeat_move.builtin_F_expr</span><span class="p">,</span> <span class="p">{</span> <span class="n">expr</span> <span class="o">=</span> <span class="kc">true</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">({</span> <span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;x&#34;</span><span class="p">,</span> <span class="s2">&#34;o&#34;</span> <span class="p">},</span> <span class="s2">&#34;t&#34;</span><span class="p">,</span> <span class="n">ts_repeat_move.builtin_t_expr</span><span class="p">,</span> <span class="p">{</span> <span class="n">expr</span> <span class="o">=</span> <span class="kc">true</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">({</span> <span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;x&#34;</span><span class="p">,</span> <span class="s2">&#34;o&#34;</span> <span class="p">},</span> <span class="s2">&#34;T&#34;</span><span class="p">,</span> <span class="n">ts_repeat_move.builtin_T_expr</span><span class="p">,</span> <span class="p">{</span> <span class="n">expr</span> <span class="o">=</span> <span class="kc">true</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span>
</span></span></code></pre></div><h4 id="incremental-selection">incremental selection</h4>
<p>You can now do this via the LSP instead of the textobjects plugin, so I added the following plugins:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;x&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;c-space&gt;&#34;</span><span class="p">,</span> <span class="kr">function</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">vim.lsp</span><span class="p">.</span><span class="n">buf.selection_range</span><span class="p">(</span><span class="s2">&#34;outer&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span><span class="p">,</span> <span class="p">{</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Expand selection (incremental)&#34;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;x&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;M-space&gt;&#34;</span><span class="p">,</span> <span class="kr">function</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">vim.lsp</span><span class="p">.</span><span class="n">buf.selection_range</span><span class="p">(</span><span class="s2">&#34;inner&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span><span class="p">,</span> <span class="p">{</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Shrink selection (incremental)&#34;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;c-space&gt;&#34;</span><span class="p">,</span> <span class="kr">function</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">vim.cmd</span><span class="p">(</span><span class="s2">&#34;normal! v&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">vim.lsp</span><span class="p">.</span><span class="n">buf.selection_range</span><span class="p">(</span><span class="s2">&#34;outer&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span><span class="p">,</span> <span class="p">{</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Start incremental selection&#34;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;x&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;c-s&gt;&#34;</span><span class="p">,</span> <span class="kr">function</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">vim.lsp</span><span class="p">.</span><span class="n">buf.selection_range</span><span class="p">(</span><span class="s2">&#34;outer&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span><span class="p">,</span> <span class="p">{</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Expand to scope&#34;</span> <span class="p">})</span>
</span></span></code></pre></div><h2 id="neotest">Neotest</h2>
<p>I thought that would be enough, but I noticed in my <code>~/.local/state/nvim/neotest.log</code> that I was getting the following error:</p>
<pre tabindex="0"><code>WARN | 2025-11-01T20:10:40Z+0000 | ...eovimPackages/opt/neotest/lua/neotest/lib/subprocess.lua:203 | CHILD | Error in remote call ...Packages/opt/neotest/lua/neotest/lib/treesitter/init.lua:130: attempt to index a nil value
stack traceback:
    ...Packages/opt/neotest/lua/neotest/lib/treesitter/init.lua:130: in function &#39;get__parse_root&#39;
    ...Packages/opt/neotest/lua/neotest/lib/treesitter/init.lua:162: in function &#39;parse_positions_from_string&#39;
    ...Packages/opt/neotest/lua/neotest/lib/treesitter/init.lua:209: in function &#39;func&#39;
    ...eovimPackages/opt/neotest/lua/neotest/lib/subprocess.lua:195: in function &lt;...eovimPackages/opt/neotest/lua/neotest/lib/subprocess.lua:194&gt;
    [C]: in function &#39;xpcall&#39;
    ...eovimPackages/opt/neotest/lua/neotest/lib/subprocess.lua:194: in function &lt;...eovimPackages/opt/neotest/lua/neotest/lib/subprocess.lua:193&gt;
WARN | 2025-11-01T20:10:40Z+0000 | ...eovimPackages/opt/neotest/lua/neotest/lib/subprocess.lua:203 | CHILD | Error in remote call ...Packages/opt/neotest/lua/neotest/lib/treesitter/init.lua:130: attempt to index a nil val
</code></pre><p>I found this <a href="https://github.com/nvim-neotest/neotest/issues/552">issue</a> and this <a href="https://github.com/nvim-neotest/neotest/pull/548">fix</a>. Then I updated Neotest to the latest version by again adding it as an input:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="n">plugins</span><span class="o">-</span><span class="n">neotest</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;github:nvim-neotest/neotest&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">flake</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>and</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="n">test</span> <span class="err">=</span> <span class="k">with</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">vimPlugins</span><span class="p">;</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">  <span class="n">pkgs</span><span class="o">.</span><span class="n">neovimPlugins</span><span class="o">.</span><span class="n">neotest</span>
</span></span><span class="line"><span class="cl"><span class="p">];</span>
</span></span></code></pre></div><h2 id="finally-fixed-">Finally fixed? 🙏</h2>
<p>Then I ran <code>nh home switch</code> (similar to <code>home-manager switch --flake ~/nixicle#haseeb@workstation</code>). I reloaded Neovim, and voila, it works now! That was my day of yak shaving and fixing random broken stuff. 🐐</p>
<p>I guess it goes without saying that if you&rsquo;re running on the bleeding edge, things will break. The advantage is that usually only one thing breaks at a time, whereas with a major upgrade, multiple things might break, and you have to fix them all at once.</p>
<p>Also, this <a href="https://www.youtube.com/watch?v=CrJUhtHdGQ8">classic video</a> is a must-watch!!!! 🤣</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL - How to Change Emojis in nh</title>
      <link>https://haseebmajid.dev/posts/2025-08-10-til-how-to-change-emojis-in-nh/</link>
      <pubDate>Sun, 10 Aug 2025 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2025-08-10-til-how-to-change-emojis-in-nh/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL - How to Change Emojis in nh&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Recently I have been trying to update the emojis when I run &lt;a href=&#34;https://github.com/nix-community/nh&#34;&gt;nh&lt;/a&gt;. Particularly
the nom output.&lt;/p&gt;
&lt;p&gt;The old output was like so:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;To fix this update your nh derivation like so:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;environment&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;systemPackages&lt;/span&gt; &lt;span class=&#34;err&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;with&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;pkgs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;pkgs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;nh&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;override&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;nix-output-monitor&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;pkgs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;nix-output-monitor&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;overrideAttrs&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;old&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;n&#34;&gt;postPatch&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;old&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;postPatch&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;or&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s1&#34;&gt;        substituteInPlace lib/NOM/Print.hs \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s1&#34;&gt;          --replace &amp;#39;down = &amp;#34;↓&amp;#34;&amp;#39; &amp;#39;down = &amp;#34;\xf072e&amp;#34;&amp;#39; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s1&#34;&gt;          --replace &amp;#39;up = &amp;#34;↑&amp;#34;&amp;#39; &amp;#39;up = &amp;#34;\xf0737&amp;#34;&amp;#39; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s1&#34;&gt;          --replace &amp;#39;clock = &amp;#34;⏱&amp;#34;&amp;#39; &amp;#39;clock = &amp;#34;\xf520&amp;#34;&amp;#39; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s1&#34;&gt;          --replace &amp;#39;running = &amp;#34;⏵&amp;#34;&amp;#39; &amp;#39;running = &amp;#34;\xf04b&amp;#34;&amp;#39; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s1&#34;&gt;          --replace &amp;#39;done = &amp;#34;✔&amp;#34;&amp;#39; &amp;#39;done = &amp;#34;\xf00c&amp;#34;&amp;#39; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s1&#34;&gt;          --replace &amp;#39;todo = &amp;#34;⏸&amp;#34;&amp;#39; &amp;#39;todo = &amp;#34;\xf04d&amp;#34;&amp;#39; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s1&#34;&gt;          --replace &amp;#39;warning = &amp;#34;⚠&amp;#34;&amp;#39; &amp;#39;warning = &amp;#34;\xf071&amp;#34;&amp;#39; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s1&#34;&gt;          --replace &amp;#39;average = &amp;#34;∅&amp;#34;&amp;#39; &amp;#39;average = &amp;#34;\xf1da&amp;#34;&amp;#39; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s1&#34;&gt;          --replace &amp;#39;bigsum = &amp;#34;∑&amp;#34;&amp;#39; &amp;#39;bigsum = &amp;#34;\xf04a0&amp;#34;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s1&#34;&gt;      &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note you will no longer be able to use the default nix cache to build this. So everytime nh updates you will need to
manually build it.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL - How to Change Emojis in nh</strong></p>
<p>Recently I have been trying to update the emojis when I run <a href="https://github.com/nix-community/nh">nh</a>. Particularly
the nom output.</p>
<p>The old output was like so:</p>
<p></p>
<p>To fix this update your nh derivation like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="n">environment</span><span class="o">.</span><span class="n">systemPackages</span> <span class="err">=</span> <span class="k">with</span> <span class="n">pkgs</span><span class="p">;</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">  <span class="p">(</span><span class="n">pkgs</span><span class="o">.</span><span class="n">nh</span><span class="o">.</span><span class="n">override</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">nix-output-monitor</span> <span class="o">=</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">nix-output-monitor</span><span class="o">.</span><span class="n">overrideAttrs</span> <span class="p">(</span><span class="n">old</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">postPatch</span> <span class="o">=</span> <span class="n">old</span><span class="o">.</span><span class="n">postPatch</span> <span class="n">or</span> <span class="s2">&#34;&#34;</span> <span class="o">+</span> <span class="s1">&#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">        substituteInPlace lib/NOM/Print.hs \
</span></span></span><span class="line"><span class="cl"><span class="s1">          --replace &#39;down = &#34;↓&#34;&#39; &#39;down = &#34;\xf072e&#34;&#39; \
</span></span></span><span class="line"><span class="cl"><span class="s1">          --replace &#39;up = &#34;↑&#34;&#39; &#39;up = &#34;\xf0737&#34;&#39; \
</span></span></span><span class="line"><span class="cl"><span class="s1">          --replace &#39;clock = &#34;⏱&#34;&#39; &#39;clock = &#34;\xf520&#34;&#39; \
</span></span></span><span class="line"><span class="cl"><span class="s1">          --replace &#39;running = &#34;⏵&#34;&#39; &#39;running = &#34;\xf04b&#34;&#39; \
</span></span></span><span class="line"><span class="cl"><span class="s1">          --replace &#39;done = &#34;✔&#34;&#39; &#39;done = &#34;\xf00c&#34;&#39; \
</span></span></span><span class="line"><span class="cl"><span class="s1">          --replace &#39;todo = &#34;⏸&#34;&#39; &#39;todo = &#34;\xf04d&#34;&#39; \
</span></span></span><span class="line"><span class="cl"><span class="s1">          --replace &#39;warning = &#34;⚠&#34;&#39; &#39;warning = &#34;\xf071&#34;&#39; \
</span></span></span><span class="line"><span class="cl"><span class="s1">          --replace &#39;average = &#34;∅&#34;&#39; &#39;average = &#34;\xf1da&#34;&#39; \
</span></span></span><span class="line"><span class="cl"><span class="s1">          --replace &#39;bigsum = &#34;∑&#34;&#39; &#39;bigsum = &#34;\xf04a0&#34;&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">      &#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="p">];</span>
</span></span></code></pre></div><p>Note you will no longer be able to use the default nix cache to build this. So everytime nh updates you will need to
manually build it.</p>
<p></p>
<p>That&rsquo;s it!</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Voxicle Build Log 16</title>
      <link>https://haseebmajid.dev/posts/2025-07-07-voxicle-build-log-16/</link>
      <pubDate>Mon, 04 Aug 2025 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2025-07-07-voxicle-build-log-16/</guid>
      <description>&lt;h2 id=&#34;-previous-build-log-objectives&#34;&gt;⏮️ Previous Build Log Objectives&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Finish the setting page&lt;/li&gt;
&lt;li&gt;Improve observability
&lt;ul&gt;
&lt;li&gt;Send data to LGTM Grafana Cloud&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;-what-i-worked-on&#34;&gt;🛠️ What I Worked On&lt;/h2&gt;
&lt;h3 id=&#34;observability&#34;&gt;Observability&lt;/h3&gt;
&lt;p&gt;This was more for my Gophercon talk, but I fixed the local LGTM stack setup so that we can send logs, metrics and traces
and view it all in Grafana. Then correleate them correctly and move between them in the GUI.&lt;/p&gt;
&lt;p&gt;Worked on the terraform code to then create the same stack in Grafana cloud to setup the LGTM stack. Including deploying
an alloy agent on my VPS. So we can send all of our OTLP data their and have consumed by the Grafana cloud.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="-previous-build-log-objectives">⏮️ Previous Build Log Objectives</h2>
<ul>
<li>Finish the setting page</li>
<li>Improve observability
<ul>
<li>Send data to LGTM Grafana Cloud</li>
</ul>
</li>
</ul>
<h2 id="-what-i-worked-on">🛠️ What I Worked On</h2>
<h3 id="observability">Observability</h3>
<p>This was more for my Gophercon talk, but I fixed the local LGTM stack setup so that we can send logs, metrics and traces
and view it all in Grafana. Then correleate them correctly and move between them in the GUI.</p>
<p>Worked on the terraform code to then create the same stack in Grafana cloud to setup the LGTM stack. Including deploying
an alloy agent on my VPS. So we can send all of our OTLP data their and have consumed by the Grafana cloud.</p>
<h3 id="setting-page">Setting Page</h3>
<p>Started work on the settings page to allow the user to update various settings about the organization.
Such as the name, logo and description.</p>
<p>Added ways for user to change their org name, avatar and description.</p>
<h2 id="-wins">✅ Wins</h2>
<ul>
<li>Learnt a lot more about OTLP observability</li>
</ul>
<h2 id="-challenges">⚠️ Challenges</h2>
<ul>
<li>Didn&rsquo;t work much on this project got busy with real life</li>
</ul>
<h2 id="-what-i-learned">💡 What I Learned</h2>
<ul>
<li>How to correlate logs, traces and metrics in the Grafana UI using the LGTM stack.</li>
</ul>
<h2 id="-next-build-log-objectives">⏭️ Next Build Log Objectives</h2>
<ul>
<li>Finish the setting page</li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Use Tailscale GitLab CI to Deploy to Our Nix Home lab</title>
      <link>https://haseebmajid.dev/posts/2025-07-10-how-to-use-tailscale-gitlab-ci-to-deploy-to-our-nix-homelab/</link>
      <pubDate>Thu, 10 Jul 2025 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2025-07-10-how-to-use-tailscale-gitlab-ci-to-deploy-to-our-nix-homelab/</guid>
      <description>&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;p&gt;So I have a small home lab, where I have a few random things running, like a GitLab CI runner, a media server etc.
All my home lab servers (3 of them) are running NixOS and are defined within my Nix flake what services they run.&lt;/p&gt;
&lt;p&gt;For example: &lt;a href=&#34;https://gitlab.com/hmajid2301/nixicle/-/blob/main/systems/x86_64-linux/ms01/default.nix?ref_type=heads&#34;&gt;https://gitlab.com/hmajid2301/nixicle/-/blob/main/systems/x86_64-linux/ms01/default.nix?ref_type=heads&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;They are set up using &lt;a href=&#34;https://haseebmajid.dev/posts/2024-05-02-part-5b-installing-our-nix-configuration-as-part-of-your-workflow&#34;&gt;nixos-anywhere like most of my other services&lt;/a&gt;. Then using deploy-rs we can ssh onto our home lab servers and deploy nix config, i.e. updating packages.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="background">Background</h2>
<p>So I have a small home lab, where I have a few random things running, like a GitLab CI runner, a media server etc.
All my home lab servers (3 of them) are running NixOS and are defined within my Nix flake what services they run.</p>
<p>For example: <a href="https://gitlab.com/hmajid2301/nixicle/-/blob/main/systems/x86_64-linux/ms01/default.nix?ref_type=heads">https://gitlab.com/hmajid2301/nixicle/-/blob/main/systems/x86_64-linux/ms01/default.nix?ref_type=heads</a></p>
<p>They are set up using <a href="/posts/2024-05-02-part-5b-installing-our-nix-configuration-as-part-of-your-workflow">nixos-anywhere like most of my other services</a>. Then using deploy-rs we can ssh onto our home lab servers and deploy nix config, i.e. updating packages.</p>
<p>I wanted a more automated way to do, i.e. anytime nixicle repo changes, try to deploy those changes to my home lab on CI.</p>
<h2 id="tailscale">Tailscale</h2>
<p>I connect to my home lab using Tailscale a very cool tool, think like a VPN, it uses WireGuard behind the scenes.
With some extra stuff on top to provide a really nice user experience. Highly recommend if you are looking to get into homelabbing.</p>
<p>So first, our CI runner would need to be able to connect to Tailscale to be able to deploy our new config onto the home lab
servers.</p>
<p>I will assume you have already set up your Tailscale account for the next part. I may create a tutorial later on how
you can do it. Better to be honest, there is a ton of decent literature available already, including videos.</p>
<p>First, let&rsquo;s create an auth key. Go to: <a href="https://login.tailscale.com/admin/settings/keys">https://login.tailscale.com/admin/settings/keys</a></p>
<p><img
        loading="lazy"
        src="/posts/2025-07-10-how-to-use-tailscale-gitlab-ci-to-deploy-to-our-nix-homelab/images/auth_key.png"
        type=""
        alt="Auth Key"
        
      /></p>
<p>We want to enable Ephemeral, so the device gets removed from our list, else every time CI runs we will get a new
device in our list of machines. Then, in my case, I need to also check the pre-approve, so the admin doesn&rsquo;t need to
manually approve it, slightly defeating the purpose of CI.</p>
<p>You can also optionally create a tag for your machines using this auth key. In my case, I wanted to tag it with CI.
Which we can then use for ACL rules etc.</p>
<h2 id="gitlab-ci">Gitlab CI</h2>
<p>After creating the key copy the value, you won&rsquo;t be able to see it again, so keep that in mind. Add it to your CI
variables, you can check the masked option, so the auth key isn&rsquo;t accidentally printed in CI.</p>
<p>(You can access this using the left side bar; setting &gt; CI/CD &gt; Variables)</p>
<p><img
        loading="lazy"
        src="/posts/2025-07-10-how-to-use-tailscale-gitlab-ci-to-deploy-to-our-nix-homelab/images/ci_vars.png"
        type=""
        alt="Tailscale CI Vars"
        
      /></p>
<p><img
        loading="lazy"
        src="/posts/2025-07-10-how-to-use-tailscale-gitlab-ci-to-deploy-to-our-nix-homelab/images/ci_var.png"
        type=""
        alt="Tailscale CI Var"
        
      /></p>
<p>Then our CI file may look something like this <code>.gitlab-ci.yml</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">nixos/nix</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">deploy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">tailscale</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">deploy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">rules</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">if</span><span class="p">:</span><span class="w"> </span><span class="l">$CI_PIPELINE_SOURCE == &#34;merge_request_event&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">mkdir -p /etc/nix/ /etc/ssh/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">echo &#34;experimental-features = nix-command flakes&#34; &gt;&gt; /etc/nix/nix.conf</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">nix-env -iA nixpkgs.docker nixpkgs.go-task nixpkgs.flyctl nixpkgs.tailscale nixpkgs.jq</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">ssh-keygen -t ed25519 -N &#34;&#34; -f ~/.ssh/id_ed25519</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">ssh-keygen -t ed25519 -N &#34;&#34; -f /etc/ssh/ssh_host_ed25519_key</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">mkdir -p ~/.ssh</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">chmod 700 ~/.ssh</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">echo &#34;Host *&#34; &gt;&gt; ~/.ssh/config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">echo &#34;  StrictHostKeyChecking no&#34; &gt;&gt; ~/.ssh/config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">echo &#34;  UserKnownHostsFile /dev/null&#34; &gt;&gt; ~/.ssh/config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">export SSH_ASKPASS_REQUIRE=never</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">eval $(ssh-agent -s)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">ssh-add ~/.ssh/id_ed25519 2&gt;/dev/null</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">tailscaled --state=&#34;mem:&#34; --statedir=/var/lib/tailscale &amp;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">tailscale up --authkey=${TAILSCALE_AUTHKEY} --hostname=&#34;gitlab-$(cat /etc/hostname)&#34; --accept-routes --ssh</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">nix profile install github:serokell/deploy-rs</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">deploy &#34;.#ms01&#34; --hostname ms01 --ssh-user nixos --skip-checks --confirm-timeout &#34;600&#34; --remote-build</span><span class="w">
</span></span></span></code></pre></div><p>Let&rsquo;s break this file down</p>
<p>First, we want to create some SSH keys so that you can SSH on the CI runner onto the home lab server.</p>
<p>The next part we start up the Tailscale daemon in the background, and then we authenticate with Tailscale so our
runner can now connect to our home lab servers</p>
<p>Then, finally, we install deploy-rs and use it to SSH to our machines and deploy to it.</p>
<h2 id="tailscale-ssh">Tailscale SSH</h2>
<p><img
        loading="lazy"
        src="/posts/2025-07-10-how-to-use-tailscale-gitlab-ci-to-deploy-to-our-nix-homelab/images/ssh.png"
        type=""
        alt="SSH"
        
      /></p>
<p>I also had to enable Tailscale ssh on the host devices so we could SSH from the runner without needing to include
an SSH private key and adding that to the allowed SSH list on each server. Which we can do like so:</p>
<p><code>tailscale set --ssh</code></p>
<p>This lets Tailscale take over SSH for us, users can still SSH like normal, but you can also SSH like normal as well.
But just make sure you are aware of the security implications before you turn it on.</p>
<p>Then I set up on ACLs like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl">	<span class="s2">&#34;tagOwners&#34;</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nt">&#34;tag:ci&#34;</span><span class="p">:</span>           <span class="p">[],</span>
</span></span><span class="line"><span class="cl">		<span class="nt">&#34;tag:homelab&#34;</span><span class="p">:</span>      <span class="p">[],</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span><span class="err">,</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// Define access control lists for users, groups, autogroups, tags,
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="c1">// Tailscale IP addresses, and subnet ranges.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="s2">&#34;acls&#34;</span><span class="err">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">		<span class="c1">// Allow all connections.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>		<span class="c1">// Comment this section out if you want to define specific restrictions.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>		<span class="p">{</span><span class="nt">&#34;action&#34;</span><span class="p">:</span> <span class="s2">&#34;accept&#34;</span><span class="p">,</span> <span class="nt">&#34;src&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;tag:ci&#34;</span><span class="p">],</span> <span class="nt">&#34;dst&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;tag:homelab:*&#34;</span><span class="p">]},</span>
</span></span><span class="line"><span class="cl">		<span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="nt">&#34;action&#34;</span><span class="p">:</span> <span class="s2">&#34;accept&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">			<span class="nt">&#34;src&#34;</span><span class="p">:</span>    <span class="p">[</span><span class="s2">&#34;tag:homelab&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">			<span class="nt">&#34;dst&#34;</span><span class="p">:</span>    <span class="p">[</span><span class="s2">&#34;tag:homelab:*&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">		<span class="p">},</span>
</span></span><span class="line"><span class="cl">	<span class="p">]</span><span class="err">,</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// Define users and devices that can use Tailscale SSH.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="s2">&#34;ssh&#34;</span><span class="err">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">		<span class="c1">// Allow all users to SSH into their own devices in check mode.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>		<span class="c1">// Comment this section out if you want to define specific restrictions.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>		<span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="nt">&#34;action&#34;</span><span class="p">:</span> <span class="s2">&#34;accept&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">			<span class="nt">&#34;src&#34;</span><span class="p">:</span>    <span class="p">[</span><span class="s2">&#34;autogroup:member&#34;</span><span class="p">,</span> <span class="s2">&#34;tag:ci&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">			<span class="nt">&#34;dst&#34;</span><span class="p">:</span>    <span class="p">[</span><span class="s2">&#34;tag:homelab&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">			<span class="nt">&#34;users&#34;</span><span class="p">:</span>  <span class="p">[</span><span class="s2">&#34;autogroup:nonroot&#34;</span><span class="p">,</span> <span class="s2">&#34;root&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">		<span class="p">},</span>
</span></span><span class="line"><span class="cl">		<span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="nt">&#34;action&#34;</span><span class="p">:</span> <span class="s2">&#34;accept&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">			<span class="nt">&#34;src&#34;</span><span class="p">:</span>    <span class="p">[</span><span class="s2">&#34;tag:ci&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">			<span class="nt">&#34;dst&#34;</span><span class="p">:</span>    <span class="p">[</span><span class="s2">&#34;tag:homelab&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">			<span class="nt">&#34;users&#34;</span><span class="p">:</span>  <span class="p">[</span><span class="s2">&#34;nixos&#34;</span><span class="p">,</span> <span class="s2">&#34;root&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">		<span class="p">},</span>
</span></span><span class="line"><span class="cl">	<span class="p">]</span><span class="err">,</span>
</span></span></code></pre></div><p>I am no expert on ACL rules in Tailscale, but I think this allows the CI runner to SSH onto the home lab servers.
Which I, of course, have already tagged with home lab.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Fix Shell Completions When Using Nix Direnv and Fish Shell</title>
      <link>https://haseebmajid.dev/posts/2025-07-02-how-to-fix-shell-completions-when-using-nix-direnv/</link>
      <pubDate>Wed, 02 Jul 2025 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2025-07-02-how-to-fix-shell-completions-when-using-nix-direnv/</guid>
      <description>&lt;p&gt;If you read this &lt;a href=&#34;https://haseebmajid.dev/posts/2024-09-12-til-how-to-get-shell-completions-in-nix-shell-with-direnv/#issues&#34;&gt;post&lt;/a&gt;,
I finally managed to figure out how to get shell completions in fish shell when you install a tool using
a devshell in Nix, while using direnv. This plugin makes sure that the fish shell completions get resynced.&lt;/p&gt;
&lt;p&gt;Currently, I am using a fork of the &lt;a href=&#34;https://github.com/pfgray/fish-completion-sync&#34;&gt;original plugin&lt;/a&gt;,
as the fish shell v4 broke it but eventually can use the original.&lt;/p&gt;
&lt;p&gt;How it works:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Fish will search $fish_complete_path dynamically, so the idea is to implement a function which listens for changes to $XDG_DATA_DIRS, and attempts to keep that in sync with $fish_complete_path.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>If you read this <a href="/posts/2024-09-12-til-how-to-get-shell-completions-in-nix-shell-with-direnv/#issues">post</a>,
I finally managed to figure out how to get shell completions in fish shell when you install a tool using
a devshell in Nix, while using direnv. This plugin makes sure that the fish shell completions get resynced.</p>
<p>Currently, I am using a fork of the <a href="https://github.com/pfgray/fish-completion-sync">original plugin</a>,
as the fish shell v4 broke it but eventually can use the original.</p>
<p>How it works:</p>
<blockquote>
<p>Fish will search $fish_complete_path dynamically, so the idea is to implement a function which listens for changes to $XDG_DATA_DIRS, and attempts to keep that in sync with $fish_complete_path.</p>
</blockquote>
<p>But in your nix config you can put this plugin:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">programs</span><span class="o">.</span><span class="n">fish</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">  <span class="n">plugins</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># INFO: Using this to get shell completion for programs added to the path through nix+direnv.</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Issue to upstream into direnv:Add commentMore actions</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># https://github.com/direnv/direnv/issues/443</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;completion-sync&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">src</span> <span class="o">=</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">fetchFromGitHub</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">owner</span> <span class="o">=</span> <span class="s2">&#34;iynaix&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">repo</span> <span class="o">=</span> <span class="s2">&#34;fish-completion-sync&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">rev</span> <span class="o">=</span> <span class="s2">&#34;4f058ad2986727a5f510e757bc82cbbfca4596f0&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">sha256</span> <span class="o">=</span> <span class="s2">&#34;sha256-kHpdCQdYcpvi9EFM/uZXv93mZqlk1zCi2DRhWaDyK5g=&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>According to someone who solved these issues and got in touch with me, this works on zsh: <a href="https://github.com/BronzeDeer/zsh-completion-sync">https://github.com/BronzeDeer/zsh-completion-sync</a>
Or for other shells, look here: <a href="https://github.com/direnv/direnv/issues/443">https://github.com/direnv/direnv/issues/443</a>.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Voxicle Build Log 15</title>
      <link>https://haseebmajid.dev/posts/2025-06-23-voxicle-build-log-15/</link>
      <pubDate>Mon, 23 Jun 2025 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2025-06-23-voxicle-build-log-15/</guid>
      <description>&lt;h2 id=&#34;-previous-build-log-objectives&#34;&gt;⏮️ Previous Build Log Objectives&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Mark feedback public/private&lt;/li&gt;
&lt;li&gt;Start on settings page&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;-what-i-worked-on&#34;&gt;🛠️ What I Worked On&lt;/h2&gt;
&lt;h3 id=&#34;mark-feedback-as-privatepublic&#34;&gt;Mark feedback as private/public&lt;/h3&gt;
&lt;p&gt;Logged in users can mark feedback as private so it won&amp;rsquo;t be shown on the public dashboard.&lt;/p&gt;
&lt;h3 id=&#34;gitlab-dependency-proxy&#34;&gt;Gitlab Dependency Proxy&lt;/h3&gt;
&lt;p&gt;I started using the Gitlab dependency proxy so that we can pull in image from Docker hub using Gitlab to avoid hiting
rate limits. But also means we can use image cache which should makes jobs a quicker when pulling say &lt;code&gt;postgres&lt;/code&gt;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="-previous-build-log-objectives">⏮️ Previous Build Log Objectives</h2>
<ul>
<li>Mark feedback public/private</li>
<li>Start on settings page</li>
</ul>
<h2 id="-what-i-worked-on">🛠️ What I Worked On</h2>
<h3 id="mark-feedback-as-privatepublic">Mark feedback as private/public</h3>
<p>Logged in users can mark feedback as private so it won&rsquo;t be shown on the public dashboard.</p>
<h3 id="gitlab-dependency-proxy">Gitlab Dependency Proxy</h3>
<p>I started using the Gitlab dependency proxy so that we can pull in image from Docker hub using Gitlab to avoid hiting
rate limits. But also means we can use image cache which should makes jobs a quicker when pulling say <code>postgres</code>.</p>
<h3 id="add-dev-environment">Add Dev Environment</h3>
<p>Previously there was just a local and production environment. Now there is a development environment we can deploy to.
This is now done CI on a MR via GitLab CI.</p>
<p>It is available at: <a href="https://dev.voxicle.app">https://dev.voxicle.app</a></p>
<h3 id="fix-generate-ci-job">Fix generate CI job</h3>
<p>For a while the CI job to check if generated code was correct was failing with extra CSS classes in the tailwind generated
file. For the life of me I couldn&rsquo;t work out why. Recently I decided to have a crack, testing it locally with <code>gitlab-ci-local</code>.
In the end it seemed to be caused by mockery. After moving mockery under the tailwind cli tool it seemed to work fine.
But again I have no clue why, it had extra classes like <code>sr-only</code> but doing a grep (or ripgrep) for the generated mocks
there was no <code>sr-only</code>. So I still don&rsquo;t know why this fixed anything. Will need to investigate more later.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">  </span><span class="nt">generate</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">desc</span><span class="p">:</span><span class="w"> </span><span class="l">Generates all the code needed for the project i.e. sqlc, templ &amp; tailwindcss</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">cmds</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">templ generate</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">tailwindcss -i ./static/css/tailwind.css -o ./static/css/styles.css --minify</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">mockery --all</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">sqlc generate</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">gomod2nix generate</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">task</span><span class="p">:</span><span class="w"> </span><span class="l">format</span><span class="w">
</span></span></span></code></pre></div><h2 id="-wins">✅ Wins</h2>
<h2 id="-challenges">⚠️ Challenges</h2>
<ul>
<li>Getting the GitLab proxy to work took longer than expected</li>
<li>Trying to work out how much is left in this app before I should start sharing with people and collecting real feedback</li>
<li>Why moving <code>mockery</code> under <code>tailwindcss</code> works</li>
</ul>
<h2 id="-what-i-learned">💡 What I Learned</h2>
<ul>
<li>The  GitLab dependency proxy doesn&rsquo;t work for personal projects, they need to be in a group i.e. not hmajid2301/voxicle but voxicle/voxicle</li>
</ul>
<h2 id="-next-build-log-objectives">⏭️ Next Build Log Objectives</h2>
<ul>
<li>Finish the setting page</li>
<li>Improve observability
<ul>
<li>Send data to LGTM Grafana Cloud</li>
</ul>
</li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Use the GitLab Proxy in CI</title>
      <link>https://haseebmajid.dev/posts/2025-06-18-how-to-use-the-gitlab-proxy-in-ci/</link>
      <pubDate>Wed, 18 Jun 2025 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2025-06-18-how-to-use-the-gitlab-proxy-in-ci/</guid>
      <description>&lt;details
  class=&#34;notice warning&#34;
  open=&#34;true&#34;
&gt;
    &lt;summary class=&#34;notice-title&#34;&gt;Personal Projects&lt;/summary&gt;
  
  &lt;p&gt;For the moment, the GitLab proxy will not work if the project is in your personal namespace
i.e. &lt;code&gt;gitlab.com/hmajid2301&lt;/code&gt;. So to make it work with my project Voxicle I had to move to its own group.&lt;/p&gt;
&lt;p&gt;GitLab Issue: &lt;a href=&#34;https://gitlab.com/gitlab-org/gitlab/-/issues/323773&#34;&gt;https://gitlab.com/gitlab-org/gitlab/-/issues/323773&lt;/a&gt;&lt;/p&gt;

&lt;/details&gt;

&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;p&gt;The GitLab dependency proxy, allows us to cache Docker images that we pull from Docker Hub. This allows us to
reduce bandwidth and speed up builds, i.e. quicker to pull Postgres for our tests in CI.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">Personal Projects</summary>
  
  <p>For the moment, the GitLab proxy will not work if the project is in your personal namespace
i.e. <code>gitlab.com/hmajid2301</code>. So to make it work with my project Voxicle I had to move to its own group.</p>
<p>GitLab Issue: <a href="https://gitlab.com/gitlab-org/gitlab/-/issues/323773">https://gitlab.com/gitlab-org/gitlab/-/issues/323773</a></p>

</details>

<h2 id="background">Background</h2>
<p>The GitLab dependency proxy, allows us to cache Docker images that we pull from Docker Hub. This allows us to
reduce bandwidth and speed up builds, i.e. quicker to pull Postgres for our tests in CI.</p>
<p>It can also avoid us potentially hitting rate limits with Docker Hub as well. Especially in groups that have a lot of
projects that are constantly running in CI.</p>
<h2 id="setup">Setup</h2>
<p>First go to your group and enable the Dependency Proxy and potentially the automatic cache clean up if you like.</p>
<p><img
        loading="lazy"
        src="/posts/2025-06-18-how-to-use-the-gitlab-proxy-in-ci/images/settings.png"
        type=""
        alt="Settings in GitLab"
        
      /></p>
<p>Then we can now do <code>${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/postgres:17.4</code> to pull images. You can set
that up in GitLab, CI.</p>
<p>In my case, some of my jobs use the <code>nixos/nix</code> so in GitLab CI I use:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/nixos/nix</span><span class="w">
</span></span></span></code></pre></div><p>I also use docker-compose in CI:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">tests:integration</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">extends</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">.test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">docker compose --profile test up -d</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">task tests:integration -- ${GOTEST_EXTRA_ARGS}</span><span class="w">
</span></span></span></code></pre></div><p>We need to authenticate with the proxy, and you can do in CI like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">.test</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">docker:dind</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">before_script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">docker login -u &#34;$CI_DEPENDENCY_PROXY_USER&#34; -p &#34;$CI_DEPENDENCY_PROXY_PASSWORD&#34; &#34;$CI_DEPENDENCY_PROXY_SERVER&#34;</span><span class="w">
</span></span></span></code></pre></div><p>Then we can update our <code>docker-compose.yml</code> file like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">postgres</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX:-docker.io}/postgres:17.4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">profiles</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">5432</span><span class="p">:</span><span class="m">5432</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">POSTGRES_USER</span><span class="p">:</span><span class="w"> </span><span class="l">postgres</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">POSTGRES_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">postgres</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">postgres-data:/var/lib/postgresql/data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./docker/postgres-init.sql:/docker-entrypoint-initdb.d/init.sql</span><span class="w">
</span></span></span></code></pre></div><p>You will notice <code>:-docker.io</code> this means that if the environment variable is not set for the dependency proxy.
It defaults to <code>docker.io</code> so this docker compose file will work locally and in CI.</p>
<p>But for non Docker Hub images you cannot use the dependency proxy, so we leave it like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">  </span><span class="nt">smocker</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">ghcr.io/smocker-dev/smocker:1.0.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">profiles</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">other</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">44300</span><span class="p">:</span><span class="m">8080</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">44301</span><span class="p">:</span><span class="m">8081</span><span class="w">
</span></span></span></code></pre></div><p>That&rsquo;s it! You can now use the GitLab dependency proxy, which should help speed up CI a bit.</p>
<h2 id="variables">Variables</h2>
<ul>
<li>CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX pulls through the top-level group.</li>
<li>CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX pulls through the subgroup, or direct group the project exists in.</li>
</ul>
<h2 id="links">Links</h2>
<ul>
<li><a href="https://docs.gitlab.com/user/packages/dependency_proxy/">https://docs.gitlab.com/user/packages/dependency_proxy/</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Voxicle Build Log 14</title>
      <link>https://haseebmajid.dev/posts/2025-06-02-voxicle-build-log-14/</link>
      <pubDate>Mon, 09 Jun 2025 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2025-06-02-voxicle-build-log-14/</guid>
      <description>&lt;h2 id=&#34;-previous-build-log-objectives&#34;&gt;⏮️ Previous Build Log Objectives&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Public page for feedback
&lt;ul&gt;
&lt;li&gt;private and public feedbacks&lt;/li&gt;
&lt;li&gt;allow anonymous voting&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;-what-i-worked-on&#34;&gt;🛠️ What I Worked On&lt;/h2&gt;
&lt;h3 id=&#34;public-feedback-dashboard&#34;&gt;Public Feedback Dashboard&lt;/h3&gt;
&lt;p&gt;We started of with this:&lt;/p&gt;
&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2025-06-02-voxicle-build-log-14/images/old_public_dashboard.png&#34;
        type=&#34;&#34;
        alt=&#34;Old Dashboard&#34;
        
      /&gt;&lt;/p&gt;
&lt;p&gt;And finish with this&lt;/p&gt;
&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2025-06-02-voxicle-build-log-14/images/new_public_dashboard.png&#34;
        type=&#34;&#34;
        alt=&#34;New Dashboard&#34;
        
      /&gt;&lt;/p&gt;
&lt;p&gt;Users can now do the almost everything they can when logged in but when anonymous. We attach a &lt;code&gt;deviceID&lt;/code&gt; cookie to them
to be able to spot if its the same user. This only works for the same device and until they clear their cookies but
should be good enough for most of the users I think for now.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="-previous-build-log-objectives">⏮️ Previous Build Log Objectives</h2>
<ul>
<li>Public page for feedback
<ul>
<li>private and public feedbacks</li>
<li>allow anonymous voting</li>
</ul>
</li>
</ul>
<h2 id="-what-i-worked-on">🛠️ What I Worked On</h2>
<h3 id="public-feedback-dashboard">Public Feedback Dashboard</h3>
<p>We started of with this:</p>
<p><img
        loading="lazy"
        src="/posts/2025-06-02-voxicle-build-log-14/images/old_public_dashboard.png"
        type=""
        alt="Old Dashboard"
        
      /></p>
<p>And finish with this</p>
<p><img
        loading="lazy"
        src="/posts/2025-06-02-voxicle-build-log-14/images/new_public_dashboard.png"
        type=""
        alt="New Dashboard"
        
      /></p>
<p>Users can now do the almost everything they can when logged in but when anonymous. We attach a <code>deviceID</code> cookie to them
to be able to spot if its the same user. This only works for the same device and until they clear their cookies but
should be good enough for most of the users I think for now.</p>
<h3 id="devex-improvements">DevEx improvements</h3>
<p>Add <code>mprocs</code> to be able to nicely manage running multiple commands in parallel and need leaving floating processes running.
Such as being ableto watch file changes with templ and tailwindcss and also start the hot reloaded web service with air.</p>
<p><img
        loading="lazy"
        src="/posts/2025-06-02-voxicle-build-log-14/images/mprocs_dev.png"
        type=""
        alt="mprocs dev task"
        
      />
<img
        loading="lazy"
        src="/posts/2025-06-02-voxicle-build-log-14/images/mprocs_watch.png"
        type=""
        alt="mprocs watch task"
        
      /></p>
<p>Try to run dependencies using <code>docker-compose</code> vs services in Gitlab CI. So we don&rsquo;t have to make changes in multiple
places.</p>
<p>Also started a new tab in zellij to be able to run some common commands easily, I just need to go into the pane
and hit enter i.e. <code>zellij run -- task lint</code>.</p>
<p><img
        loading="lazy"
        src="/posts/2025-06-02-voxicle-build-log-14/images/zellij.png"
        type=""
        alt="zellij"
        
      /></p>
<h3 id="opentofu">OpenTofu</h3>
<p>There are lots of stuff I setup manually and I would like to move into code, declaratively using terraform/open tofu.
In this case:</p>
<ul>
<li>cloudflare DNS</li>
</ul>
<h3 id="observability">Observability</h3>
<p>I have been working on my slides for my Gophercon talk about otel and observability, so I have been fixing gaps
and the broken setup in Voxicle. So now we have logs, metrics and traces viewable in a locally running instance of Grafana.
It will be easy enough to set this up in production to point to which ever tool I decide to use.</p>
<h2 id="-wins">✅ Wins</h2>
<ul>
<li>Subdomains take you to voxicle i.e. <code>orga.voxicle.app</code></li>
<li>Public page shows public feedbacks</li>
<li>Made some DevEx improvements, taskfiles, gitlab CI</li>
<li>Made my infra setup more declarative with OpenTofu</li>
</ul>
<h2 id="-challenges">⚠️ Challenges</h2>
<ul>
<li>Getting podman to work with the <code>dind</code> service in Gitlab CI
<ul>
<li>Going down a rabbit hole of trying to run podman vs using the Docker daemon inside</li>
</ul>
</li>
</ul>
<h2 id="-what-i-learned">💡 What I Learned</h2>
<ul>
<li>How to setup open tofu new project</li>
</ul>
<h2 id="-next-build-log-objectives">⏭️ Next Build Log Objectives</h2>
<ul>
<li>Mark feedback public/private</li>
<li>Start on settings page</li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Voxicle Build Log 13</title>
      <link>https://haseebmajid.dev/posts/2025-05-19-voxicle-build-log-week-13/</link>
      <pubDate>Mon, 19 May 2025 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2025-05-19-voxicle-build-log-week-13/</guid>
      <description>&lt;p&gt;I will stop making these weekly I think and produce them when there is actual stuff to share, as not make them
stale and boring. Some times they may end up weekly, depending on how much progress I am making.&lt;/p&gt;
&lt;h2 id=&#34;-previous-build-log-objectives&#34;&gt;⏮️ Previous Build Log Objectives&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;✅ Better error modals (fixed)&lt;/li&gt;
&lt;li&gt;✅ Start working on public page&lt;/li&gt;
&lt;li&gt;✅ Multi tenant sub domains
&lt;ul&gt;
&lt;li&gt;i.e. org1.voxicle.app&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;-what-i-worked-on&#34;&gt;🛠️ What I Worked On&lt;/h2&gt;
&lt;h3 id=&#34;error-modal-bug&#34;&gt;Error Modal Bug&lt;/h3&gt;
&lt;p&gt;I have some middleware that will show an error modal when we return an error back to the client. I may change this to
be a toast for certain actions vs a full on modal but we can do that in the middleware.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I will stop making these weekly I think and produce them when there is actual stuff to share, as not make them
stale and boring. Some times they may end up weekly, depending on how much progress I am making.</p>
<h2 id="-previous-build-log-objectives">⏮️ Previous Build Log Objectives</h2>
<ul>
<li>✅ Better error modals (fixed)</li>
<li>✅ Start working on public page</li>
<li>✅ Multi tenant sub domains
<ul>
<li>i.e. org1.voxicle.app</li>
</ul>
</li>
</ul>
<h2 id="-what-i-worked-on">🛠️ What I Worked On</h2>
<h3 id="error-modal-bug">Error Modal Bug</h3>
<p>I have some middleware that will show an error modal when we return an error back to the client. I may change this to
be a toast for certain actions vs a full on modal but we can do that in the middleware.</p>
<p>There was a bug where the modal wouldn&rsquo;t work insted it would return the status code and HTML like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl">401 Unauthorized (Failed to login.)<span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>...<span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>Set <code>HX-Retarget</code>, to replace other items i.e. not <code>#feedback_list</code> but we want to update <code>#error_modal_container</code>.
So in the middleware we now set this header, with an error modal.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">w</span><span class="p">.</span><span class="nf">Header</span><span class="p">().</span><span class="nf">Set</span><span class="p">(</span><span class="s">&#34;HX-Retarget&#34;</span><span class="p">,</span> <span class="s">&#34;#error_modal_container&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">w</span><span class="p">.</span><span class="nf">Header</span><span class="p">().</span><span class="nf">Set</span><span class="p">(</span><span class="s">&#34;Content-Type&#34;</span><span class="p">,</span> <span class="s">&#34;text/html&#34;</span><span class="p">)</span>
</span></span></code></pre></div><p>With the following script, to show the modal after a HTMX swap:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;htmx:afterSwap&#39;</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">evt</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="k">if</span> <span class="p">(</span><span class="nx">evt</span><span class="p">.</span><span class="nx">detail</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">id</span> <span class="o">===</span> <span class="s1">&#39;error_modal_container&#39;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s1">&#39;error_modal&#39;</span><span class="p">)</span><span class="o">?</span><span class="p">.</span><span class="nx">showModal</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span></code></pre></div><h3 id="hx-indicator-trigger">hx-indicator trigger</h3>
<p>For really fast request we don&rsquo;t want to show the loading spinner quickly and then hide it. So for the first x seconds
don&rsquo;t show the loading spinner wait say 1 second then show it. So it is less likely to flash on screen and disappear.
Which doesn&rsquo;t provide an amazing experience for the user, of course there is a chance that this could still happen.</p>
<p>I fixed it by doing something like this, when we make a request to our backend.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">span</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;delete-indicator&#34;</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;hidden justify-center items-center transition duration-300 delay-2000 hx-indicator&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">span</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;loading loading-spinner&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">span</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">span</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;ml-2&#34;</span><span class="p">&gt;</span>Sending...<span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span>
</span></span></code></pre></div><h3 id="public-feedback-page">Public Feedback Page</h3>
<p>One of the main features of the web app is allowing other people to vote on features, so we allow anonymous votes.
So decided to manage anonymous votes in its own table, which keeps track of a device_id.  A long lived cookie we
populate. Which of course means you can vote again from another device. But this is a good enough trade off.</p>
<p>So started work to show the feedbacks on a single public page, which a user can then filter on like the private one
but then also vote for it anonymously or if you are logged in the vote counts as normal.</p>
<h3 id="subdomains">Subdomains</h3>
<p>Part of the public page is allowing the app to handle subdomains i.e. <code>org1.voxicle.app/public/feedback</code> to show
the public feedback page.</p>
<h2 id="refactored-schema">Refactored Schema</h2>
<p>Remove organization_id and replaced with slug as this is already unique and means we have to do fewer looks up to the DB.
Involved a long migration script and updating tests and some code as well.</p>
<h2 id="-wins">✅ Wins</h2>
<ul>
<li>Error modal fixed</li>
<li>Worked out how to handle anonymous votes</li>
</ul>
<h2 id="-challenges">⚠️ Challenges</h2>
<ul>
<li>Not understanding why <code>hx-swap-oob</code> wouldn&rsquo;t work with <code>HX-Retarget</code>.</li>
</ul>
<h2 id="-what-i-learned">💡 What I Learned</h2>
<ul>
<li>(remembered) HTMX only swaps by default on 200 HTTP Status code
<ul>
<li><code>&lt;meta name=&quot;htmx-config&quot; content='{&quot;responseHandling&quot;: [{&quot;code&quot;:&quot;...&quot;, &quot;swap&quot;: true}]}'/&gt;</code></li>
</ul>
</li>
<li><code>hx-swap-oob</code> used for other changes first container is replaced, we can use <code>hx-retarget</code></li>
</ul>
<h2 id="-next-build-log-objectives">⏭️ Next Build Log Objectives</h2>
<ul>
<li>Public page for feedback
<ul>
<li>private and public feedbacks</li>
<li>allow anonymous voting</li>
</ul>
</li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Part 8: Neovim as Part of Your Development Workflow</title>
      <link>https://haseebmajid.dev/posts/2025-05-15-part-8-neovim-as-part-of-your-development-workflow/</link>
      <pubDate>Thu, 15 May 2025 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2025-05-15-part-8-neovim-as-part-of-your-development-workflow/</guid>
      <description>&lt;p&gt;After another 5 months (again) I&amp;rsquo;m finally back blogging about my development setup, opps!&lt;/p&gt;
&lt;h2 id=&#34;what-is-neovim&#34;&gt;What is Neovim?&lt;/h2&gt;
&lt;p&gt;Neovim is a text editor, that you can run in your terminal. Much like &lt;code&gt;vim&lt;/code&gt; and &lt;code&gt;vi&lt;/code&gt; or &lt;code&gt;nano&lt;/code&gt;. So we would run
this inside our terminal emulator and Zellij. It differs from vim because you can configure it using lua vs vimscript
. Some of the other unique features it had, you can now do in vim (I believe). Such as tree sitter support and LSPs.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>After another 5 months (again) I&rsquo;m finally back blogging about my development setup, opps!</p>
<h2 id="what-is-neovim">What is Neovim?</h2>
<p>Neovim is a text editor, that you can run in your terminal. Much like <code>vim</code> and <code>vi</code> or <code>nano</code>. So we would run
this inside our terminal emulator and Zellij. It differs from vim because you can configure it using lua vs vimscript
. Some of the other unique features it had, you can now do in vim (I believe). Such as tree sitter support and LSPs.</p>
<h2 id="why-neovim">Why Neovim?</h2>
<p>So why Neovim, good question! Before Neovim, I was using VS Code, and I was mostly pretty happy. But in 2023,
I decided to give Neovim a go:</p>
<ul>
<li>As most things with me and using Linux, I like to be able to customise and personalise things for myself</li>
<li>Requires fewer resources to run compared with my old editor</li>
<li>Runs in the terminal so can use it with tmux</li>
<li>Forced me to learn my tools a lot better</li>
</ul>
<p>I was already using vim binding since 2013, someone at university told me to learn vim, and I never looked back.
This then fit so well and let me include tmux/Zellij in my workflow, and things became a lot clearer. In terms of
one project per session.</p>
<p>Some less known plugins</p>
<ul>
<li>oil.nvim</li>
<li>headlines.nvim</li>
<li>nvim-treesitter-textobjects</li>
<li>grug-far.nvim</li>
<li>dropbar.nvim</li>
</ul>
<h2 id="how-i-configured-it">How I configured it?</h2>
<p>Until recently, I used NixVim which was all my Neovim config in Nix. Whilst this meant everything was in Nix
and that was nice. However, it created a single lua file, and meant I had to like lua code in strings, losing the LSP.</p>
<p>So I decided to move my config to NixCats, where we can install our dependencies using Nix so we get all the benefits
of Nix. Then our config is <code>lua</code>, I think when you have so many files and complex structure it makes sense to write
in the language the config should be in. Not via Nix as this sorta of proxy.</p>
<p>The config is pretty messy and needs a tidy up, many to-dos, but it works and was basically feature parity with
my old config. It also made me more familiar with setting up Neovim in lua itself.</p>
<h3 id="dependencies">dependencies</h3>
<p>Due to how NixCats works, we can split our dependencies into categories, then enable and disable as we need.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="n">lspsAndRuntimeDeps</span> <span class="err">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">general</span> <span class="o">=</span> <span class="k">with</span> <span class="n">pkgs</span><span class="p">;</span> <span class="p">[</span> <span class="n">universal-ctags</span> <span class="n">ripgrep</span> <span class="n">fd</span> <span class="n">stdenv</span><span class="o">.</span><span class="n">cc</span><span class="o">.</span><span class="n">cc</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="n">css</span> <span class="o">=</span> <span class="k">with</span> <span class="n">pkgs</span><span class="p">;</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="n">stylelint</span>
</span></span><span class="line"><span class="cl">      <span class="n">prettierd</span>
</span></span><span class="line"><span class="cl">      <span class="n">rustywind</span>
</span></span><span class="line"><span class="cl">      <span class="n">tailwindcss-language-server</span>
</span></span><span class="line"><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="n">docker</span> <span class="o">=</span> <span class="k">with</span> <span class="n">pkgs</span><span class="p">;</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="n">dockerfile-language-server-nodejs</span>
</span></span><span class="line"><span class="cl">      <span class="n">docker-compose-language-service</span>
</span></span><span class="line"><span class="cl">      <span class="n">hadolint</span>
</span></span><span class="line"><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="n">html</span> <span class="o">=</span> <span class="k">with</span> <span class="n">pkgs</span><span class="p">;</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="n">htmlhint</span>
</span></span><span class="line"><span class="cl">      <span class="n">rubyPackages_3_4</span><span class="o">.</span><span class="n">htmlbeautifier</span>
</span></span><span class="line"><span class="cl">      <span class="n">htmx-lsp</span>
</span></span><span class="line"><span class="cl">      <span class="n">vscode-langservers-extracted</span>
</span></span><span class="line"><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="n">go</span> <span class="o">=</span> <span class="k">with</span> <span class="n">pkgs</span><span class="p">;</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="n">go</span>
</span></span><span class="line"><span class="cl">      <span class="n">golangci-lint</span>
</span></span><span class="line"><span class="cl">      <span class="n">delve</span>
</span></span><span class="line"><span class="cl">      <span class="n">gopls</span>
</span></span><span class="line"><span class="cl">      <span class="n">go-tools</span>
</span></span><span class="line"><span class="cl">      <span class="n">gotools</span>
</span></span><span class="line"><span class="cl">      <span class="n">gotestsum</span>
</span></span><span class="line"><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">optionalPlugins</span> <span class="err">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">debug</span> <span class="o">=</span> <span class="k">with</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">vimPlugins</span><span class="p">;</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="n">nvim-dap</span>
</span></span><span class="line"><span class="cl">      <span class="n">pkgs</span><span class="o">.</span><span class="n">neovimPlugins</span><span class="o">.</span><span class="n">nvim-dap-view</span>
</span></span><span class="line"><span class="cl">      <span class="n">nvim-dap-go</span>
</span></span><span class="line"><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="n">test</span> <span class="o">=</span> <span class="k">with</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">vimPlugins</span><span class="p">;</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="n">neotest</span>
</span></span><span class="line"><span class="cl">      <span class="n">neotest-golang</span>
</span></span><span class="line"><span class="cl">      <span class="n">nvim-coverage</span>
</span></span><span class="line"><span class="cl">      <span class="n">vim-dotenv</span>
</span></span><span class="line"><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="n">lint</span> <span class="o">=</span> <span class="k">with</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">vimPlugins</span><span class="p">;</span> <span class="p">[</span> <span class="n">nvim-lint</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="n">format</span> <span class="o">=</span> <span class="k">with</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">vimPlugins</span><span class="p">;</span> <span class="p">[</span> <span class="n">conform-nvim</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="setup">Setup</h3>
<p>We can then set up different versions of our Neovim config, here we are bundling the config with the Neovim script.
So to introduce changes to config we need to rebuild our config. Which makes it more reproducible. But can be annoying
when we are testing out changes and don&rsquo;t want to do a full rebuild every time (<code>wrapRC</code>).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl">  <span class="c1"># see :help nixCats.flake.outputs.packageDefinitions</span>
</span></span><span class="line"><span class="cl">  <span class="n">packageDefinitions</span><span class="o">.</span><span class="n">replace</span> <span class="err">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">nixCats</span> <span class="o">=</span> <span class="p">{</span> <span class="n">pkgs</span><span class="o">,</span> <span class="o">...</span> <span class="p">}:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">settings</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">wrapRc</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">suffix-path</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">suffix-LD</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">aliases</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;vimCat&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="n">configDirName</span> <span class="o">=</span> <span class="s2">&#34;nixCats-nvim&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">      <span class="n">categories</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">general</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">go</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">ai</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">diagnostics</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">editor</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">debug</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">test</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">lint</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">format</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">git</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span></code></pre></div><p>To deal with this issue, we can do something like this, where we pass the unwrappedCfgPath where our lua config is.
We can reload it by closing Neovim and opening it up again, vs doing a full rebuild of say home-manager.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="n">regularCats</span> <span class="err">=</span> <span class="p">{</span> <span class="n">pkgs</span><span class="o">,</span> <span class="o">...</span> <span class="p">}:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">settings</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">wrapRc</span> <span class="o">=</span> <span class="no">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">suffix-path</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">suffix-LD</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">unwrappedCfgPath</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;</span><span class="si">${</span><span class="n">config</span><span class="o">.</span><span class="n">home</span><span class="o">.</span><span class="n">homeDirectory</span><span class="si">}</span><span class="s2">/nixicle/modules/home/cli/editors/neovim&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">configDirName</span> <span class="o">=</span> <span class="s2">&#34;nixCats-nvim&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">aliases</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;nvim&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="n">neovim-unwrapped</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl">      <span class="n">inputs</span><span class="o">.</span><span class="n">neovim-nightly-overlay</span><span class="o">.</span><span class="n">packages</span><span class="o">.</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">system</span><span class="si">}</span><span class="o">.</span><span class="n">neovim</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>So you can some of this is installing tooling, some of it installing plugins. Which we can then lazy load later.
These dependencies are all installed with Nix. So we get all of those benefits, such as reproducibility. We can
easily share our Neovim config that anyone could use on their machine. In my case, if you have flakes
you can try my Neovim config like so:</p>
<p><code>nix run .#homeConfigurations.&quot;haseeb@workstation&quot;.config.nixCats.out.packages.nixCats</code></p>
<h3 id="custom-plugins">Custom Plugins</h3>
<p>Sometimes we have plugins that aren&rsquo;t in nixpkgs yet, and it can be a while before your PR gets accepted.
Or you could create your own package that you keep updated. NixCats provides an easy way to manage this.</p>
<p>We can add this to our <code>flake.nix</code> in the input section:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="n">plugins-gx-nvim</span> <span class="err">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;github:chrishrb/gx.nvim&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">flake</span> <span class="o">=</span> <span class="no">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>NixCat will automatically make any input starting with <code>plugins-</code> available to us. Where we can then install like any
other plugin doing it like so.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="n">pkgs</span><span class="o">.</span><span class="n">neovimPlugins</span><span class="o">.</span><span class="n">gx-nvim</span>
</span></span></code></pre></div><p>Then this input will update normally when we do <code>nix flake update</code>, so we don&rsquo;t have to worry about keeping it up to
date ourselves.</p>
<p>Now onto more generic Neovim, how I have configured it. How you could set it up no matter the package manager.</p>
<h3 id="lint--formatting">lint &amp; formatting</h3>
<p>I use nvim-lint for linting, we just need to specify which linter we want to use for which file types. These tools
have all been installed using Nix in our case, so they should be available inside Neovim.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="n">require</span><span class="p">(</span><span class="s2">&#34;lint&#34;</span><span class="p">).</span><span class="n">linters_by_ft</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">docker</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&#34;hadolint&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="n">go</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&#34;golangcilint&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="n">html</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&#34;htmlhint&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="n">lua</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&#34;luacheck&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="n">nix</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&#34;statix&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="n">javascript</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&#34;eslint&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="n">typescript</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&#34;eslint&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="n">sql</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&#34;sqlfluff&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">vim.api</span><span class="p">.</span><span class="n">nvim_create_autocmd</span><span class="p">({</span> <span class="s2">&#34;BufWritePost&#34;</span> <span class="p">},</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">callback</span> <span class="o">=</span> <span class="kr">function</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">require</span><span class="p">(</span><span class="s2">&#34;lint&#34;</span><span class="p">).</span><span class="n">try_lint</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="kr">end</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span></code></pre></div><p>Then for formatting I use conform-nvim, where again we specify the formatters per file type. The full list can be found
on to conform README. You can even set up your own formatter that is not currently supported. We can also add our own
arguments to those formatters, i.e. <code>goimports</code> passing in my own local packages.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="kd">local</span> <span class="n">conform</span> <span class="o">=</span> <span class="n">require</span><span class="p">(</span><span class="s2">&#34;conform&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">conform.setup</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="n">format_on_save</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">lsp_format</span> <span class="o">=</span> <span class="s2">&#34;fallback&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">timeout_ms</span> <span class="o">=</span> <span class="mi">500</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="n">formatters</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">goimports</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">command</span> <span class="o">=</span> <span class="s2">&#34;goimports&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">args</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&#34;-local&#34;</span><span class="p">,</span> <span class="s2">&#34;gitlab.com/hmajid2301&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="n">yamlfmt</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">args</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&#34;-formatter&#34;</span><span class="p">,</span> <span class="s2">&#34;retain_line_breaks_single=true&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="n">formatters_by_ft</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">css</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&#34;prettierd&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="n">go</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&#34;gofmt&#34;</span><span class="p">,</span> <span class="s2">&#34;goimports&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="n">lua</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&#34;stylua&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="n">templ</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&#34;rustywind&#34;</span><span class="p">,</span> <span class="s2">&#34;templ&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="n">html</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&#34;htmlbeautifier&#34;</span><span class="p">,</span> <span class="s2">&#34;rustywind&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="n">nix</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&#34;nixfmt&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="n">python</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&#34;isort&#34;</span><span class="p">,</span> <span class="s2">&#34;black&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="n">javascript</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&#34;prettierd&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="n">typescript</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&#34;prettierd&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="n">sql</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&#34;sqlfluff&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="n">yaml</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&#34;yamlfmt&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span></code></pre></div><h3 id="lsp">LSP</h3>
<p>One of the most important features of my editor is to use the language server protocol (LSP) to be able to get
code completions, auto import and also show docs for functions/methods.</p>
<p>Here are some example images</p>
<p><img
        loading="lazy"
        src="/posts/2025-05-15-part-8-neovim-as-part-of-your-development-workflow/images/lsp_docs.png"
        type=""
        alt="LSP Docs"
        
      />
<img
        loading="lazy"
        src="/posts/2025-05-15-part-8-neovim-as-part-of-your-development-workflow/images/lsp_cmp.png"
        type=""
        alt="LSP CMP"
        
      /></p>
<p>Like most people, I use the nvim lspconfig plugin, which comes with a bunch of sane defaults for almost every LSP,
you might want to use.</p>
<p>Having a look at example of go where set some of my own options. Like setting build tags so that it runs correctly
where tests have build tags.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;gopls&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">for_cat</span> <span class="o">=</span> <span class="s2">&#34;go&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">lsp</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">settings</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">gopls</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">buildFlags</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&#34;-tags=unit,integration,e2e,bdd,dind&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">                <span class="n">staticcheck</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">directoryFilters</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&#34;-.git&#34;</span><span class="p">,</span> <span class="s2">&#34;-.vscode&#34;</span><span class="p">,</span> <span class="s2">&#34;-.idea&#34;</span><span class="p">,</span> <span class="s2">&#34;-.vscode-test&#34;</span><span class="p">,</span> <span class="s2">&#34;-node_modules&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">                <span class="n">semanticTokens</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">codelenses</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="n">gc_details</span> <span class="o">=</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">generate</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">regenerate_cgo</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">run_govulncheck</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">test</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">tidy</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">upgrade_dependency</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">vendor</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="p">},</span>
</span></span><span class="line"><span class="cl">                <span class="n">hints</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="n">assignVariableTypes</span> <span class="o">=</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">compositeLiteralFields</span> <span class="o">=</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">compositeLiteralTypes</span> <span class="o">=</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">constantValues</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">functionTypeParameters</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">parameterNames</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">rangeVariableTypes</span> <span class="o">=</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="p">},</span>
</span></span><span class="line"><span class="cl">                <span class="n">analyses</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="n">assign</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">bools</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">defers</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">deprecated</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">tests</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">nilness</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">httpresponse</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">unmarshal</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">unusedparams</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">unusedwrite</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">useany</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="p">},</span>
</span></span><span class="line"><span class="cl">            <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">},</span>
</span></span></code></pre></div><p>I also have some key maps for navigating, such as <code>&lt;leader&gt;gd</code> for going to where something is defined, i.e. a function
in the stdlib which will go and find the code.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="n">nmap</span><span class="p">(</span><span class="s2">&#34;&lt;leader&gt;cr&#34;</span><span class="p">,</span> <span class="n">vim.lsp</span><span class="p">.</span><span class="n">buf.rename</span><span class="p">,</span> <span class="s2">&#34;[R]e[n]ame&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">nmap</span><span class="p">(</span><span class="s2">&#34;&lt;leader&gt;gd&#34;</span><span class="p">,</span> <span class="n">vim.lsp</span><span class="p">.</span><span class="n">buf.definition</span><span class="p">,</span> <span class="s2">&#34;[G]oto [D]efinition&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">nmap</span><span class="p">(</span><span class="s2">&#34;&lt;leader&gt;gr&#34;</span><span class="p">,</span> <span class="kr">function</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">require</span><span class="p">(</span><span class="s2">&#34;telescope.builtin&#34;</span><span class="p">).</span><span class="n">lsp_references</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span><span class="p">,</span> <span class="s2">&#34;[G]oto [R]eferences&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">nmap</span><span class="p">(</span><span class="s2">&#34;&lt;leader&gt;gi&#34;</span><span class="p">,</span> <span class="kr">function</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">require</span><span class="p">(</span><span class="s2">&#34;telescope.builtin&#34;</span><span class="p">).</span><span class="n">lsp_implementations</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span><span class="p">,</span> <span class="s2">&#34;[G]oto [I]mplementation&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">nmap</span><span class="p">(</span><span class="s2">&#34;&lt;leader&gt;ds&#34;</span><span class="p">,</span> <span class="kr">function</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">require</span><span class="p">(</span><span class="s2">&#34;telescope.builtin&#34;</span><span class="p">).</span><span class="n">lsp_document_symbols</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span><span class="p">,</span> <span class="s2">&#34;[D]ocument [S]ymbols&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">nmap</span><span class="p">(</span><span class="s2">&#34;&lt;leader&gt;ws&#34;</span><span class="p">,</span> <span class="kr">function</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">require</span><span class="p">(</span><span class="s2">&#34;telescope.builtin&#34;</span><span class="p">).</span><span class="n">lsp_dynamic_workspace_symbols</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span><span class="p">,</span> <span class="s2">&#34;[W]orkspace [S]ymbols&#34;</span><span class="p">)</span>
</span></span></code></pre></div><h3 id="debugging">debugging</h3>
<p>Another important feature is being able to debug, again like LSP, there are a debugging protocol we can use DAP.</p>
<p>I am using</p>
<ul>
<li>nvim-dap</li>
<li>nvim-dap-go</li>
<li>nvim-dap-view</li>
</ul>
<p>Where some of the config looks like this, to show automatically show dap-view when the debugger starts.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="kd">local</span> <span class="n">dap</span><span class="p">,</span> <span class="n">dv</span> <span class="o">=</span> <span class="n">require</span><span class="p">(</span><span class="s2">&#34;dap&#34;</span><span class="p">),</span> <span class="n">require</span><span class="p">(</span><span class="s2">&#34;dap-view&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">dap.listeners</span><span class="p">.</span><span class="n">before.attach</span><span class="p">[</span><span class="s2">&#34;dap-view-config&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="kr">function</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">dv.open</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span>
</span></span><span class="line"><span class="cl"><span class="n">dap.listeners</span><span class="p">.</span><span class="n">before.launch</span><span class="p">[</span><span class="s2">&#34;dap-view-config&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="kr">function</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">dv.open</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span>
</span></span><span class="line"><span class="cl"><span class="n">dap.listeners</span><span class="p">.</span><span class="n">before.event_terminated</span><span class="p">[</span><span class="s2">&#34;dap-view-config&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="kr">function</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">dv.close</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span>
</span></span><span class="line"><span class="cl"><span class="n">dap.listeners</span><span class="p">.</span><span class="n">before.event_exited</span><span class="p">[</span><span class="s2">&#34;dap-view-config&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="kr">function</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">dv.close</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">vim.fn</span><span class="p">.</span><span class="n">sign_define</span><span class="p">(</span><span class="s2">&#34;DapBreakpoint&#34;</span><span class="p">,</span> <span class="p">{</span> <span class="n">text</span> <span class="o">=</span> <span class="s2">&#34; &#34;</span><span class="p">,</span> <span class="n">texthl</span> <span class="o">=</span> <span class="s2">&#34;DapBreakpoint&#34;</span><span class="p">,</span> <span class="n">linehl</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="n">numhl</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="n">vim.fn</span><span class="p">.</span><span class="n">sign_define</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;DapBreakpointCondition&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="n">text</span> <span class="o">=</span> <span class="s2">&#34; &#34;</span><span class="p">,</span> <span class="n">texthl</span> <span class="o">=</span> <span class="s2">&#34;DapBreakpointCondition&#34;</span><span class="p">,</span> <span class="n">linehl</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="n">numhl</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">vim.fn</span><span class="p">.</span><span class="n">sign_define</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;DapBreakpointRejected&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="n">text</span> <span class="o">=</span> <span class="s2">&#34; &#34;</span><span class="p">,</span> <span class="n">texthl</span> <span class="o">=</span> <span class="s2">&#34;DiagnosticError&#34;</span><span class="p">,</span> <span class="n">linehl</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="n">numhl</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">vim.fn</span><span class="p">.</span><span class="n">sign_define</span><span class="p">(</span><span class="s2">&#34;DapLogPoint&#34;</span><span class="p">,</span> <span class="p">{</span> <span class="n">text</span> <span class="o">=</span> <span class="s2">&#34; &#34;</span><span class="p">,</span> <span class="n">texthl</span> <span class="o">=</span> <span class="s2">&#34;DapBreakpoint&#34;</span><span class="p">,</span> <span class="n">linehl</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="n">numhl</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="n">vim.fn</span><span class="p">.</span><span class="n">sign_define</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;DapStopped&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="n">text</span> <span class="o">=</span> <span class="s2">&#34;󰁕 &#34;</span><span class="p">,</span> <span class="n">texthl</span> <span class="o">=</span> <span class="s2">&#34;DapStopped&#34;</span><span class="p">,</span> <span class="n">linehl</span> <span class="o">=</span> <span class="s2">&#34;DapStopped&#34;</span><span class="p">,</span> <span class="n">numhl</span> <span class="o">=</span> <span class="s2">&#34;DapStopped&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></div><p>Some of you may be wondering why I am not using the dap-ui plugin that most people use. I couldn&rsquo;t find a layout
that worked for me, so hence I decided to go with a simpler dap-view plugin. Then some key maps to hover the
current values of variables.</p>
<p><img
        loading="lazy"
        src="/posts/2025-05-15-part-8-neovim-as-part-of-your-development-workflow/images/debug.png"
        type=""
        alt="Debug"
        
      /></p>
<p>I use the built-in widgets to do it, I don&rsquo;t think this toggle is quite working, so I might just remove all this code.
We can close that panel with the variables by doing <code>ZZ</code> (and other ways to close a buffer).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="kd">local</span> <span class="n">dap_widgets</span> <span class="o">=</span> <span class="n">require</span><span class="p">(</span><span class="s2">&#34;dap.ui.widgets&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">local</span> <span class="n">debug_widgets</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">scopes</span> <span class="o">=</span> <span class="kc">nil</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">hover</span> <span class="o">=</span> <span class="kc">nil</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">local</span> <span class="kr">function</span> <span class="nf">create_scopes_widget</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="kr">return</span> <span class="n">dap_widgets.centered_float</span><span class="p">(</span><span class="n">dap_widgets.scopes</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">border</span> <span class="o">=</span> <span class="s2">&#34;rounded&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">width</span> <span class="o">=</span> <span class="mi">200</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">height</span> <span class="o">=</span> <span class="mi">25</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">local</span> <span class="kr">function</span> <span class="nf">create_hover_widget</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="kr">return</span> <span class="n">dap_widgets.hover</span><span class="p">(</span><span class="kc">nil</span><span class="p">,</span> <span class="p">{</span> <span class="n">border</span> <span class="o">=</span> <span class="s2">&#34;rounded&#34;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">local</span> <span class="kr">function</span> <span class="nf">safe_toggle</span><span class="p">(</span><span class="n">widget_type</span><span class="p">,</span> <span class="n">creator_fn</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="kr">return</span> <span class="kr">function</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="kd">local</span> <span class="n">current</span> <span class="o">=</span> <span class="n">debug_widgets</span><span class="p">[</span><span class="n">widget_type</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">-- Close if exists</span>
</span></span><span class="line"><span class="cl">        <span class="kr">if</span> <span class="n">current</span> <span class="kr">then</span>
</span></span><span class="line"><span class="cl">            <span class="n">pcall</span><span class="p">(</span><span class="kr">function</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">                <span class="c1">-- Close window and clear buffer directly</span>
</span></span><span class="line"><span class="cl">                <span class="kr">if</span> <span class="n">current.winid</span> <span class="ow">and</span> <span class="n">vim.api</span><span class="p">.</span><span class="n">nvim_win_is_valid</span><span class="p">(</span><span class="n">current.winid</span><span class="p">)</span> <span class="kr">then</span>
</span></span><span class="line"><span class="cl">                    <span class="n">vim.api</span><span class="p">.</span><span class="n">nvim_win_close</span><span class="p">(</span><span class="n">current.winid</span><span class="p">,</span> <span class="kc">true</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="kr">end</span>
</span></span><span class="line"><span class="cl">                <span class="kr">if</span> <span class="n">current.bufnr</span> <span class="ow">and</span> <span class="n">vim.api</span><span class="p">.</span><span class="n">nvim_buf_is_valid</span><span class="p">(</span><span class="n">current.bufnr</span><span class="p">)</span> <span class="kr">then</span>
</span></span><span class="line"><span class="cl">                    <span class="n">vim.api</span><span class="p">.</span><span class="n">nvim_buf_delete</span><span class="p">(</span><span class="n">current.bufnr</span><span class="p">,</span> <span class="p">{</span> <span class="n">force</span> <span class="o">=</span> <span class="kc">true</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl">                <span class="kr">end</span>
</span></span><span class="line"><span class="cl">            <span class="kr">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">debug_widgets</span><span class="p">[</span><span class="n">widget_type</span><span class="p">]</span> <span class="o">=</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="cl">            <span class="kr">return</span>
</span></span><span class="line"><span class="cl">        <span class="kr">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">-- Create and open new widget</span>
</span></span><span class="line"><span class="cl">        <span class="kd">local</span> <span class="n">new_widget</span> <span class="o">=</span> <span class="n">creator_fn</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">new_widget.open</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">debug_widgets</span><span class="p">[</span><span class="n">widget_type</span><span class="p">]</span> <span class="o">=</span> <span class="n">new_widget</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">-- Track window closure</span>
</span></span><span class="line"><span class="cl">        <span class="n">vim.api</span><span class="p">.</span><span class="n">nvim_create_autocmd</span><span class="p">(</span><span class="s2">&#34;WinClosed&#34;</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">pattern</span> <span class="o">=</span> <span class="n">tostring</span><span class="p">(</span><span class="n">new_widget.winid</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">once</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">callback</span> <span class="o">=</span> <span class="kr">function</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">                <span class="n">debug_widgets</span><span class="p">[</span><span class="n">widget_type</span><span class="p">]</span> <span class="o">=</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="cl">            <span class="kr">end</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="kr">end</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">-- Keymaps</span>
</span></span><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;n&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&lt;leader&gt;dv&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">safe_toggle</span><span class="p">(</span><span class="s2">&#34;scopes&#34;</span><span class="p">,</span> <span class="n">create_scopes_widget</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Debug: Toggle Scopes&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;n&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&lt;leader&gt;dV&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">safe_toggle</span><span class="p">(</span><span class="s2">&#34;hover&#34;</span><span class="p">,</span> <span class="n">create_hover_widget</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Debug: Toggle Variables&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></div><p>Then finally some key maps:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;leader&gt;dc&#34;</span><span class="p">,</span> <span class="n">dap.continue</span><span class="p">,</span> <span class="p">{</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Debug: Start/Continue&#34;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;F5&gt;&#34;</span><span class="p">,</span> <span class="n">dap.continue</span><span class="p">,</span> <span class="p">{</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Debug: Start/Continue&#34;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;F6&gt;&#34;</span><span class="p">,</span> <span class="n">dap.step_into</span><span class="p">,</span> <span class="p">{</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Debug: Step Into&#34;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;F7&gt;&#34;</span><span class="p">,</span> <span class="n">dap.step_over</span><span class="p">,</span> <span class="p">{</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Debug: Step Over&#34;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;F8&gt;&#34;</span><span class="p">,</span> <span class="n">dap.step_out</span><span class="p">,</span> <span class="p">{</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Debug: Step Out&#34;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;leader&gt;db&#34;</span><span class="p">,</span> <span class="n">dap.toggle_breakpoint</span><span class="p">,</span> <span class="p">{</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Debug: Toggle Breakpoint&#34;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;leader&gt;dr&#34;</span><span class="p">,</span> <span class="kr">function</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">dap.disconnect</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">dap.close</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">dap.run_last</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span><span class="p">,</span> <span class="p">{</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Debug: Restart&#34;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;leader&gt;dp&#34;</span><span class="p">,</span> <span class="n">dap.pause</span><span class="p">,</span> <span class="p">{</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Debug: Pause&#34;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;leader&gt;dl&#34;</span><span class="p">,</span> <span class="n">dap.run_last</span><span class="p">,</span> <span class="p">{</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Debug: Run the last config&#34;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;leader&gt;ds&#34;</span><span class="p">,</span> <span class="n">dap.session</span><span class="p">,</span> <span class="p">{</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Debug: Focused Session&#34;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;leader&gt;dt&#34;</span><span class="p">,</span> <span class="n">dap.terminate</span><span class="p">,</span> <span class="p">{</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Debug: Stop&#34;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;leader&gt;dC&#34;</span><span class="p">,</span> <span class="n">dap.run_to_cursor</span><span class="p">,</span> <span class="p">{</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Debug: Run to cursor&#34;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;leader&gt;dB&#34;</span><span class="p">,</span> <span class="kr">function</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">dap.set_breakpoint</span><span class="p">(</span><span class="n">vim.fn</span><span class="p">.</span><span class="n">input</span><span class="p">(</span><span class="s2">&#34;Breakpoint condition: &#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span><span class="p">,</span> <span class="p">{</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Debug: Set Breakpoint (with condition)&#34;</span> <span class="p">})</span>
</span></span></code></pre></div><p>So we can start the debugger in <code>&lt;leader&gt;dc</code>, though usually, I mostly debug my tests which I will show you how to do
that later.</p>
<p>Finally, the config for the rest of my DAP setup:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;nvim-dap-view&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">for_cat</span> <span class="o">=</span> <span class="s2">&#34;debug&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">on_plugin</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&#34;nvim-dap&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="n">after</span> <span class="o">=</span> <span class="kr">function</span><span class="p">(</span><span class="n">plugin</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">require</span><span class="p">(</span><span class="s2">&#34;dap-view&#34;</span><span class="p">).</span><span class="n">setup</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">            <span class="n">windows</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">terminal</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="n">hide</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&#34;go&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">                <span class="p">},</span>
</span></span><span class="line"><span class="cl">            <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="kr">end</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;nvim-dap-go&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">for_cat</span> <span class="o">=</span> <span class="s2">&#34;debug&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">on_plugin</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&#34;nvim-dap&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="n">after</span> <span class="o">=</span> <span class="kr">function</span><span class="p">(</span><span class="n">plugin</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">require</span><span class="p">(</span><span class="s2">&#34;dap-go&#34;</span><span class="p">).</span><span class="n">setup</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">            <span class="n">dap_configurations</span> <span class="o">=</span> <span class="p">{</span> <span class="p">{</span> <span class="n">mode</span> <span class="o">=</span> <span class="s2">&#34;remote&#34;</span><span class="p">,</span> <span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;Attach remote&#34;</span><span class="p">,</span> <span class="n">request</span> <span class="o">=</span> <span class="s2">&#34;attach&#34;</span><span class="p">,</span> <span class="n">type</span> <span class="o">=</span> <span class="s2">&#34;go&#34;</span> <span class="p">}</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">            <span class="n">delve</span> <span class="o">=</span> <span class="p">{</span> <span class="n">build_flags</span> <span class="o">=</span> <span class="s2">&#34;-tags=unit,integration,e2e,bdd,dind&#34;</span><span class="p">,</span> <span class="n">path</span> <span class="o">=</span> <span class="s2">&#34;dlv&#34;</span><span class="p">,</span> <span class="n">port</span> <span class="o">=</span> <span class="s2">&#34;40000&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="kr">end</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">},</span>
</span></span></code></pre></div><h3 id="testing">testing</h3>
<p>For testing, I use <code>neotest</code> with the <code>neotest-golang</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="n">require</span><span class="p">(</span><span class="s2">&#34;neotest&#34;</span><span class="p">).</span><span class="n">setup</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="n">adapters</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">require</span><span class="p">(</span><span class="s2">&#34;neotest-golang&#34;</span><span class="p">)({</span>
</span></span><span class="line"><span class="cl">            <span class="n">go_test_args</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&#34;-v&#34;</span><span class="p">,</span> <span class="s2">&#34;-x&#34;</span><span class="p">,</span> <span class="s2">&#34;-count=1&#34;</span><span class="p">,</span> <span class="s2">&#34;-tags=integration&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">            <span class="n">go_list_args</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&#34;-tags=unit,integration,e2e,bdd,dind&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">            <span class="n">dap_go_opts</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">delve</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="n">build_flags</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&#34;-tags=unit,integration,e2e,bdd,dind&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">                <span class="p">},</span>
</span></span><span class="line"><span class="cl">            <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">}),</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="n">output</span> <span class="o">=</span> <span class="p">{</span> <span class="n">open_on_run</span> <span class="o">=</span> <span class="kc">true</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span></code></pre></div><p>Then I have the following key maps:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;leader&gt;tt&#34;</span><span class="p">,</span> <span class="kr">function</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">neotest.run</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">vim.fn</span><span class="p">.</span><span class="n">expand</span><span class="p">(</span><span class="s2">&#34;%&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span><span class="p">,</span> <span class="p">{</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Test: Run all in current file&#34;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;leader&gt;tT&#34;</span><span class="p">,</span> <span class="kr">function</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">neotest.run</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">vim.loop</span><span class="p">.</span><span class="n">cwd</span><span class="p">())</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span><span class="p">,</span> <span class="p">{</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Test: Run all in all files&#34;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;leader&gt;tS&#34;</span><span class="p">,</span> <span class="n">neotest.run</span><span class="p">.</span><span class="n">stop</span><span class="p">,</span> <span class="p">{</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Test: Stop&#34;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;leader&gt;ts&#34;</span><span class="p">,</span> <span class="n">neotest.summary</span><span class="p">.</span><span class="n">toggle</span><span class="p">,</span> <span class="p">{</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Test: Toggle Summary&#34;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;leader&gt;tr&#34;</span><span class="p">,</span> <span class="n">neotest.run</span><span class="p">.</span><span class="n">run</span><span class="p">,</span> <span class="p">{</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Test: Run Nearest&#34;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;leader&gt;to&#34;</span><span class="p">,</span> <span class="kr">function</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">neotest.output</span><span class="p">.</span><span class="n">open</span><span class="p">({</span> <span class="n">enter</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span> <span class="n">auto_close</span> <span class="o">=</span> <span class="kc">true</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span><span class="p">,</span> <span class="p">{</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Test: Show Output&#34;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;leader&gt;td&#34;</span><span class="p">,</span> <span class="kr">function</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">neotest.run</span><span class="p">.</span><span class="n">run</span><span class="p">({</span> <span class="n">suite</span> <span class="o">=</span> <span class="kc">false</span><span class="p">,</span> <span class="n">strategy</span> <span class="o">=</span> <span class="s2">&#34;dap&#34;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span><span class="p">,</span> <span class="p">{</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Test: Debug nearest&#34;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;leader&gt;tO&#34;</span><span class="p">,</span> <span class="kr">function</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">neotest.output_panel</span><span class="p">.</span><span class="n">toggle</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span><span class="p">,</span> <span class="p">{</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Test: Toggle output&#34;</span> <span class="p">})</span>
</span></span></code></pre></div><p>The most important being the one that starts a debugger <code>&lt;leader&gt;td</code> on the nearest test. Including table tests in Go!</p>
<p>We can view all of our tests here using the test summary and run them like that.</p>
<p><img
        loading="lazy"
        src="/posts/2025-05-15-part-8-neovim-as-part-of-your-development-workflow/images/test_summary.png"
        type=""
        alt="Test Summary"
        
      /></p>
<h2 id="summary">Summary</h2>
<p>It&rsquo;s taken a long time, but I think in terms of my developer workflow, that&rsquo;s about it. In terms of the tooling, I use.
I may do another one how I work on a project and my day-to-day coding. Maybe as a YouTube video!</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Voxicle Build Log Week 12</title>
      <link>https://haseebmajid.dev/posts/2025-05-12-voxicle-build-log-week-12/</link>
      <pubDate>Mon, 12 May 2025 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2025-05-12-voxicle-build-log-week-12/</guid>
      <description>&lt;h2 id=&#34;-last-weeks-objectives&#34;&gt;⏮️ Last Weeks Objectives&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Implement Authorization using RLS in Postgres&lt;/li&gt;
&lt;li&gt;Better error modals (fixed)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;-what-i-worked-on&#34;&gt;🛠️ What I Worked On&lt;/h2&gt;
&lt;h3 id=&#34;-row-level-security-authorization&#34;&gt;🔒 Row Level Security (Authorization)&lt;/h3&gt;
&lt;p&gt;Row Level Security for Feedback and Upvotes. With row level security we can add policies and use config variables.
Such that we set the organization ID and users can only get feedback that is part of their organization.
This means we can simplify our SQL queries with fewer joins and fewer where clauses. Making them easier to read.
Essentially it lets us do a simple form of authorization inside the web app.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="-last-weeks-objectives">⏮️ Last Weeks Objectives</h2>
<ul>
<li>Implement Authorization using RLS in Postgres</li>
<li>Better error modals (fixed)</li>
</ul>
<h2 id="-what-i-worked-on">🛠️ What I Worked On</h2>
<h3 id="-row-level-security-authorization">🔒 Row Level Security (Authorization)</h3>
<p>Row Level Security for Feedback and Upvotes. With row level security we can add policies and use config variables.
Such that we set the organization ID and users can only get feedback that is part of their organization.
This means we can simplify our SQL queries with fewer joins and fewer where clauses. Making them easier to read.
Essentially it lets us do a simple form of authorization inside the web app.</p>
<p>An example on select statements for the user <code>voxicle</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">create</span><span class="w"> </span><span class="n">policy</span><span class="w"> </span><span class="n">feedback_select_policy</span><span class="w"> </span><span class="k">on</span><span class="w"> </span><span class="k">public</span><span class="p">.</span><span class="n">feedback</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">for</span><span class="w"> </span><span class="k">select</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="n">voxicle</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">using</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">organization_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">current_setting</span><span class="p">(</span><span class="s1">&#39;app.current_org_id&#39;</span><span class="p">)::</span><span class="n">uuid</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">);</span><span class="w">
</span></span></span></code></pre></div><p>Where we then have some &ldquo;hooks&rdquo; in pgx which look like this when we configure it. Where the auth is set in the ctx
in some middleware. It does make testing a bit more awkward but its a fine trade off.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">pgxConfig</span><span class="p">.</span><span class="nx">BeforeAcquire</span> <span class="p">=</span> <span class="kd">func</span><span class="p">(</span><span class="nx">a</span> <span class="nx">context</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span> <span class="nx">conn</span> <span class="o">*</span><span class="nx">pgx</span><span class="p">.</span><span class="nx">Conn</span><span class="p">)</span> <span class="kt">bool</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">state</span> <span class="o">:=</span> <span class="nx">auth</span><span class="p">.</span><span class="nf">GetFromContext</span><span class="p">(</span><span class="nx">a</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nx">state</span><span class="p">.</span><span class="nx">OrgID</span> <span class="o">!=</span> <span class="nx">uuid</span><span class="p">.</span><span class="nx">Nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">_</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">conn</span><span class="p">.</span><span class="nf">Exec</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="s">&#34;SELECT set_config(&#39;app.current_org_id&#39;, $1, false);&#34;</span><span class="p">,</span> <span class="nx">state</span><span class="p">.</span><span class="nx">OrgID</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// TODO: log the error
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="nb">panic</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">pgxConfig</span><span class="p">.</span><span class="nx">AfterRelease</span> <span class="p">=</span> <span class="kd">func</span><span class="p">(</span><span class="nx">conn</span> <span class="o">*</span><span class="nx">pgx</span><span class="p">.</span><span class="nx">Conn</span><span class="p">)</span> <span class="kt">bool</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">_</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">conn</span><span class="p">.</span><span class="nf">Exec</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">Background</span><span class="p">(),</span> <span class="s">&#34;SELECT set_config(&#39;app.current_org_id&#39;, $1, false);&#34;</span><span class="p">,</span> <span class="nx">uuid</span><span class="p">.</span><span class="nx">Nil</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// TODO: log the error
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nb">panic</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>I will do a write up/video on how I setup this up with sqlc, goose and pgx.</p>
<h3 id="-refactor">✏️ Refactor</h3>
<p>Refactored the get feedback query to not include upvotes, split that into another query. More DB queries by they are
simpler, and easier to understand now. Can change later if the app feels sluggish, needs to do some profiling.</p>
<h3 id="-tests">🧪 Tests</h3>
<p>Added more tests for upvotes, unit tests, integration tests and E2E tests (playwright).
Made it so the tests can run in parallel <code>-parallel 10</code> (GOMAXPROCS). This has caused the run time to decrease by about
50% in CI. Even though there are actually more tests now.</p>
<h2 id="-wins">✅ Wins</h2>
<ul>
<li>RLS meant we could simplify our queries</li>
<li>Added more tests</li>
<li>Made the tests run faster in CI
<ul>
<li>Run them in parallel</li>
</ul>
</li>
</ul>
<h2 id="-challenges">⚠️ Challenges</h2>
<p>Implementing RLS took a longer to implement than I expected. Spent the better part of a week on it. Has issues because
I was testing with a super user on postgres i.e. the default one in Docker. It took using AI to give me the hint what
could be the issues.</p>
<h2 id="-what-i-learned">💡 What I Learned</h2>
<p>RLS - Doesn&rsquo;t apply to super users so needed to create a separate user i.e. voxicle for local tests.
Vs using postgres the default admin user in Docker.</p>
<p>That you need to be careful running tests with <code>t.Parallel</code> in table test <a href="https://posener.github.io/go-table-driven-tests-parallel/">See more here</a>.</p>
<h2 id="-next-weeks-objectives">⏭️ Next Weeks Objectives</h2>
<ul>
<li>Better error modals (fixed)</li>
<li>Start working on public page</li>
<li>Multi tenant sub domains
<ul>
<li>i.e. org1.voxicle.app</li>
</ul>
</li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How I Setup Tailwindcss LSP With Neovim &amp; Nix (With DaisyUI)</title>
      <link>https://haseebmajid.dev/posts/2025-05-06-how-i-setup-tailwindcss-lsp-with-neovim-nix-with-daisyui-/</link>
      <pubDate>Tue, 06 May 2025 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2025-05-06-how-i-setup-tailwindcss-lsp-with-neovim-nix-with-daisyui-/</guid>
      <description>&lt;p&gt;Recently, I have been building an app (&lt;a href=&#34;https://voxicle.app&#34;&gt;https://voxicle.app&lt;/a&gt; #ShamelessPlug), with tailwind and DaisyUI. I have been
having issues getting the tailwind LSP to work nicely in Neovim, and only recently managed to make it work.
So in this article, I will go over how I set up, assuming you are using Nix as a package manager.&lt;/p&gt;
&lt;h2 id=&#34;why-nix&#34;&gt;Why Nix?&lt;/h2&gt;
&lt;p&gt;In my project, it is a go web app, so all the dependencies in the project are managed as go modules by my &lt;code&gt;go.mod&lt;/code&gt; file.
Then everything else the developer needs is set up as a dev shell. Things like the standalone tailwind CLI,
I am using a nix flake which already has DaisyUI built in so it can also generate those types i.e. &lt;code&gt;btn&lt;/code&gt; in the final
styles.css (or whatever you called it). The flake is here: &lt;a href=&#34;https://github.com/aabccd021/tailwindcss-daisyui-nix&#34;&gt;https://github.com/aabccd021/tailwindcss-daisyui-nix&lt;/a&gt;&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Recently, I have been building an app (<a href="https://voxicle.app">https://voxicle.app</a> #ShamelessPlug), with tailwind and DaisyUI. I have been
having issues getting the tailwind LSP to work nicely in Neovim, and only recently managed to make it work.
So in this article, I will go over how I set up, assuming you are using Nix as a package manager.</p>
<h2 id="why-nix">Why Nix?</h2>
<p>In my project, it is a go web app, so all the dependencies in the project are managed as go modules by my <code>go.mod</code> file.
Then everything else the developer needs is set up as a dev shell. Things like the standalone tailwind CLI,
I am using a nix flake which already has DaisyUI built in so it can also generate those types i.e. <code>btn</code> in the final
styles.css (or whatever you called it). The flake is here: <a href="https://github.com/aabccd021/tailwindcss-daisyui-nix">https://github.com/aabccd021/tailwindcss-daisyui-nix</a></p>
<p>I didn&rsquo;t want to have multiple package managers, using <code>npm</code> or something else.</p>
<p>You can learn more about this here: <a href="https://www.youtube.com/watch?v=bdGfn_ihHOk">https://www.youtube.com/watch?v=bdGfn_ihHOk</a></p>
<h2 id="setup">Setup</h2>
<p>Assuming we have enabled the tailwind LSP in our Neovim config, I am using NixCats, and it looks it like this (using nvim-lspconfig):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;tailwindcss&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">lsp</span> <span class="o">=</span> <span class="p">{},</span>
</span></span><span class="line"><span class="cl"><span class="p">},</span>
</span></span></code></pre></div><p>You can find my full config here:</p>
<ul>
<li><a href="https://gitlab.com/hmajid2301/nixicle/-/blob/8c959ca5bd6436595592d0cedf932787ba86e359/modules/home/cli/editors/neovim/lua/myLuaConf/LSPs/init.lua#L199">https://gitlab.com/hmajid2301/nixicle/-/blob/8c959ca5bd6436595592d0cedf932787ba86e359/modules/home/cli/editors/neovim/lua/myLuaConf/LSPs/init.lua#L199</a></li>
<li><a href="https://gitlab.com/hmajid2301/nixicle/-/blob/MAJ-311/modules/home/cli/editors/neovim/default.nix#L48">https://gitlab.com/hmajid2301/nixicle/-/blob/MAJ-311/modules/home/cli/editors/neovim/default.nix#L48</a></li>
</ul>
<p>So first I added this flake as an input</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="n">tailwindcss_daisy</span><span class="o">.</span><span class="n">url</span> <span class="err">=</span> <span class="s2">&#34;github:aabccd021/tailwindcss-daisyui-nix/lsp&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">tailwindcss_daisy</span><span class="o">.</span><span class="n">inputs</span><span class="o">.</span><span class="n">nixpkgs</span><span class="o">.</span><span class="n">follows</span> <span class="err">=</span> <span class="s2">&#34;nixpkgs&#34;</span><span class="p">;</span>
</span></span></code></pre></div><p>Then install the following packages:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="n">tailwindcss-language-server</span>
</span></span><span class="line"><span class="cl"><span class="n">tailwindcss_daisy</span><span class="o">.</span><span class="n">packages</span><span class="o">.</span><span class="si">${</span><span class="n">system</span><span class="si">}</span><span class="o">.</span><span class="n">default</span>
</span></span></code></pre></div><p>The <code>tailwindcss-language-server</code> being the LSP server for tailwind. Normally, this is installed in my dot files repo.
(I linked above). But this one is an overlay from the flake. So this one also has DaisyUI in its NODE_PATH.
Then it also auto-complete classes from DaisyUI.</p>
<p>Read this issue: <a href="https://github.com/aabccd021/tailwindcss-daisyui-nix/issues/1">https://github.com/aabccd021/tailwindcss-daisyui-nix/issues/1</a></p>
<p>Finally, to get the tailwind LSP to work, we need to create an empty <code>tailwind.config.js</code>. Else, the tailwind LSP does
not seem to start correctly. Even though the project is configured using the CSS file (probably will be fixed)</p>
<p>That&rsquo;s it! It took me a while to figure it out, but this seems to work pretty nicely.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Voxicle Week 11</title>
      <link>https://haseebmajid.dev/posts/2025-05-05-voxicle-week-11/</link>
      <pubDate>Mon, 05 May 2025 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2025-05-05-voxicle-week-11/</guid>
      <description>&lt;p&gt;Cricket is back this week, so I didn&amp;rsquo;t have as much time as I would over the weekend as I was out playing both weekends.
But I made progress every day even a little on Voxicle. I need to work out, what the minimum features I want before
I start to market and share with others to try it and provide feedback.&lt;/p&gt;
&lt;h2 id=&#34;last-week&#34;&gt;Last Week&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Fixed my Tailwind LSP for a better DevEx&lt;/li&gt;
&lt;li&gt;Improved my Gitlab CI file when building the dev Docker image&lt;/li&gt;
&lt;li&gt;Improved the UI&lt;/li&gt;
&lt;li&gt;Allow users to change the sort filters i.e. latest or oldest&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;next-week&#34;&gt;Next Week&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Implement Authorization using RLS in Postgres&lt;/li&gt;
&lt;li&gt;Better error modals (fixed)&lt;/li&gt;
&lt;/ul&gt;</description>
      <content:encoded><![CDATA[<p>Cricket is back this week, so I didn&rsquo;t have as much time as I would over the weekend as I was out playing both weekends.
But I made progress every day even a little on Voxicle. I need to work out, what the minimum features I want before
I start to market and share with others to try it and provide feedback.</p>
<h2 id="last-week">Last Week</h2>
<ul>
<li>Fixed my Tailwind LSP for a better DevEx</li>
<li>Improved my Gitlab CI file when building the dev Docker image</li>
<li>Improved the UI</li>
<li>Allow users to change the sort filters i.e. latest or oldest</li>
</ul>
<h2 id="next-week">Next Week</h2>
<ul>
<li>Implement Authorization using RLS in Postgres</li>
<li>Better error modals (fixed)</li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Why I Built a Web App With HTMX, Go &amp; Postgres</title>
      <link>https://haseebmajid.dev/posts/2025-05-03-why-i-build-a-web-app-with-htmx-go-postgres/</link>
      <pubDate>Mon, 05 May 2025 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2025-05-03-why-i-build-a-web-app-with-htmx-go-postgres/</guid>
      <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Recently, I have started to build apps using the web stack:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;frontend&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;htmx&lt;/li&gt;
&lt;li&gt;tailwindcss
&lt;ul&gt;
&lt;li&gt;daisyui&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;alpine&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;backend&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;go
&lt;ul&gt;
&lt;li&gt;templ&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;postgres&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;devex&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;gitlab ci&lt;/li&gt;
&lt;li&gt;nix devshells&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I have now built two apps using this stack, one of them being &lt;code&gt;banterbus.games&lt;/code&gt; a browser-based party game (currently broken).
Using web sockets, so it isn&amp;rsquo;t the most normal web application. As there are a few quirks of web sockets.&lt;/p&gt;
&lt;p&gt;Banter Bus Code: &lt;a href=&#34;https://gitlab.com/hmajid2301/banterbus&#34;&gt;https://gitlab.com/hmajid2301/banterbus&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I am currently building a more normal CRUD app, called Voxicle (&lt;a href=&#34;https://voxicle.app&#34;&gt;https://voxicle.app&lt;/a&gt;), a SaaS platform for collecting
and acting on user feedback. The first time, I am trying to build something which I will charge others for (hence
the code also not being open-source yet). Some more context here: &lt;a href=&#34;https://haseebmajid.dev/posts/2025-03-03-go-feedback-my-new-side-project/&#34;&gt;https://haseebmajid.dev/posts/2025-03-03-go-feedback-my-new-side-project/&lt;/a&gt;&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Recently, I have started to build apps using the web stack:</p>
<ul>
<li>
<p>frontend</p>
<ul>
<li>htmx</li>
<li>tailwindcss
<ul>
<li>daisyui</li>
</ul>
</li>
<li>alpine</li>
</ul>
</li>
<li>
<p>backend</p>
<ul>
<li>go
<ul>
<li>templ</li>
</ul>
</li>
<li>postgres</li>
</ul>
</li>
<li>
<p>devex</p>
<ul>
<li>gitlab ci</li>
<li>nix devshells</li>
</ul>
</li>
</ul>
<p>I have now built two apps using this stack, one of them being <code>banterbus.games</code> a browser-based party game (currently broken).
Using web sockets, so it isn&rsquo;t the most normal web application. As there are a few quirks of web sockets.</p>
<p>Banter Bus Code: <a href="https://gitlab.com/hmajid2301/banterbus">https://gitlab.com/hmajid2301/banterbus</a></p>
<p>I am currently building a more normal CRUD app, called Voxicle (<a href="https://voxicle.app">https://voxicle.app</a>), a SaaS platform for collecting
and acting on user feedback. The first time, I am trying to build something which I will charge others for (hence
the code also not being open-source yet). Some more context here: <a href="https://haseebmajid.dev/posts/2025-03-03-go-feedback-my-new-side-project/">https://haseebmajid.dev/posts/2025-03-03-go-feedback-my-new-side-project/</a></p>
<p>In this article, go over why I like this tech stack so much. Mainly because I want to keep things as simple as possible.
One other thing to note, I am a backend developer, and don&rsquo;t really like writing frontend code a la JavaScript (JS).
So this stack is designed to avoid writing as many JS as I can, which other developers of course love writing.
So read this article through those lenses. This stack might not make sense for you.</p>
<h2 id="frontend">Frontend</h2>
<p>One of the issues I found in my side projects was that I would always lose motivation when I had to write frontend
code. For a few reasons, I never had enough experience with frameworks like React or Svelte to know how to structure
them. Therefore, they would quickly become a mess.</p>
<p>Another issue is maintaining consistent state between the backend and frontend. If we drive everything from the backend
we can maintain most of our state there. Which works pretty well for a simple CRUD app we are building? The UI
doesn&rsquo;t need to do anything super crazy or clever.</p>
<h3 id="tailwindcss">TailwindCSS</h3>
<p>For styling, I have stuck with TailwindCSS for a number of years, some people hate it. I have never minded it.
It does lead to bloated class and makes the HTML look more complicated. But I never worked out how to structure
CSS. So tailwind always felt simpler to me.</p>
<p>I probably have a bunch of duplicated styles and can simplify what I have, as again I am no CSS expert nor
frontend expert. But we can look at that if we feel the frontend is slow to load etc.</p>
<h4 id="daisyui">DaisyUI</h4>
<p>I am using this component library on top of TailwindCSS, which provides us with a bunch of components and themes
we can use out of the box. Straightforward to style and tweak/change. I like it again, backend developer doing frontend
so perhaps for more complicated apps, you want your own design system and creating your own components is better.
But again, I really like how easy it makes frontend development feel for me.</p>
<h3 id="htmx">HTMX</h3>
<p>The frontend is built using HTMX, as a simple library, where we drive most of our state from the backend.
The server returns HTML (vs JSON) and then we tell it where to replace. Look at this HTMX request.
When the form is submitted, we send a POST request to the <code>/feedback</code> endpoint the body is the input fields as JSON.
(<code>json-enc</code> extension converts it to JSON for us). Then the target means the HTML returned replaces the outer HTML
<code>feedback_list</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">form</span>
</span></span><span class="line"><span class="cl">    <span class="na">hx-post</span><span class="o">=</span><span class="s">&#34;/feedback&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="na">hx-target</span><span class="o">=</span><span class="s">&#34;#feedback_list&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="na">hx-swap</span><span class="o">=</span><span class="s">&#34;outerHTML&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="na">hx-ext</span><span class="o">=</span><span class="s">&#34;json-enc&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="na">class</span><span class="o">=</span><span class="s">&#34;space-y-6&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">&gt;</span>
</span></span></code></pre></div><h3 id="alpinejs">AlpineJS</h3>
<p>I combined HTMX with AlpineJS, as some actions you don&rsquo;t always want to go to the server. Like opening a modal,
or accordion. I don&rsquo;t use it a lot and maybe could refactor my site without it. But for now, it does help
make things a bit simpler. Especially with Dynamic HTMX queries, which cannot be statically generated on the backend
when we render the HTML template.
See an example here: <a href="https://haseebmajid.dev/posts/2025-04-29-til-set-dynamic-url-with-htmx-and-alpinejs/">https://haseebmajid.dev/posts/2025-04-29-til-set-dynamic-url-with-htmx-and-alpinejs/</a></p>
<h2 id="backend">Backend</h2>
<h3 id="go">Go</h3>
<p>I moved from Python to Go about 3 years ago and honestly haven&rsquo;t looked back. I really liked statically typed
languages. Again, each to their own, but I think having typed data just makes your life so much easier. I also really
like the smaller, better devex around Go. Fast to compile. Compiles to single static binary. Easier to put into a scratch
Docker image. Simple dependency management, <code>go mod</code>. The CLI tool has a test runner (<code>go test</code>).</p>
<p>I really like the simplicity of Go, the code is pretty easy to read and see what is going on. I have tried to avoid
using frameworks like Gin, or Gorilla. In the end, I did use Fuego, so I could generate an Open API specification.</p>
<p>I started writing raw SQL with <code>sqlc</code>, so I know exactly what the query will do vs guessing with an ORM.
I loved using ORMs and frameworks like Flask or FastAPI in Python but have completely reversed my opinion in Go.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="c1">-- name: AddUser :one
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">insert</span><span class="w"> </span><span class="k">into</span><span class="w"> </span><span class="n">users</span><span class="w"> </span><span class="p">(</span><span class="n">email</span><span class="p">)</span><span class="w"> </span><span class="k">values</span><span class="w"> </span><span class="p">(</span><span class="err">$</span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="n">returning</span><span class="w"> </span><span class="o">*</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">-- name: AddOrganization :one
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">insert</span><span class="w"> </span><span class="k">into</span><span class="w"> </span><span class="n">organizations</span><span class="w"> </span><span class="p">(</span><span class="n">display_name</span><span class="p">,</span><span class="w"> </span><span class="n">slug</span><span class="p">)</span><span class="w"> </span><span class="k">values</span><span class="w"> </span><span class="p">(</span><span class="err">$</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="err">$</span><span class="mi">2</span><span class="p">)</span><span class="w"> </span><span class="n">returning</span><span class="w"> </span><span class="o">*</span><span class="p">;</span><span class="w">
</span></span></span></code></pre></div><p>Then we can generate the SQL code by running <code>sqlc generate</code> which generates go code. We are going to use <code>pgx</code> as
the underlying driver.</p>
<p>I am using the common 3 layer structure, controller (return HTML) -&gt; service layer (business logic) -&gt; store layer (DB).</p>
<h3 id="postgres">Postgres</h3>
<p>I remember I used to really like experimenting with different technologies like MongoDB. But these days boring is sexy
for me. Like this entire stack, keep it simple, stupid. Unless you have a good reason, just use Postgres. It works
there is a lot of tooling around it. For some instances, storing JSON and querying JSON, it outperforms MongoDB.</p>
<h2 id="devex">DevEx</h2>
<p>A few points on the DevEx part, I really like Gitlab CI, I self-host my own runners. So speed/running out of minutes
is not an issue. I am just very familiar with it. I use it alongside <code>go-task</code> (Taskfiles), which I found simpler
than make.</p>
<p>Finally, if you know me, I always love to mention Nix, the project is set up to use Nix dev shells for all the non go
dependencies, and we then have a really similar dev env and CI env. See more: <a href="https://www.youtube.com/watch?v=bdGfn_ihHO">https://www.youtube.com/watch?v=bdGfn_ihHO</a></p>
<p>That&rsquo;s about it! That is my high-level experience and reason for going with this tech stack for building a SaaS.
I may well go into more details about the platform I choose to help with authentication and how I intend to do
authorization but for now, this is a good starting point.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL - How to Set Dynamic URL With HTMX and AlpineJS</title>
      <link>https://haseebmajid.dev/posts/2025-04-29-til-set-dynamic-url-with-htmx-and-alpinejs/</link>
      <pubDate>Tue, 29 Apr 2025 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2025-04-29-til-set-dynamic-url-with-htmx-and-alpinejs/</guid>
      <description>&lt;p&gt;TLDR; Add this attribute to your x-bind:hx-delete &lt;code&gt;x-effect=&amp;quot;currentItemId;htmx.process($el)&amp;quot;&lt;/code&gt; as an example (adjust for your example).&lt;/p&gt;
&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;p&gt;In my app, I show the user 25 feedbacks per page, and they can delete them which opens a modal to confirm the action.
If the user presses the delete button in the modal I want to send a HTTP DELETE request to an endpoint like
&lt;code&gt;/feedback/{id}&lt;/code&gt;, but the ID is dynamic, as we are using one modal for all the feedbacks vs having one modal
per one feedback.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>TLDR; Add this attribute to your x-bind:hx-delete <code>x-effect=&quot;currentItemId;htmx.process($el)&quot;</code> as an example (adjust for your example).</p>
<h2 id="background">Background</h2>
<p>In my app, I show the user 25 feedbacks per page, and they can delete them which opens a modal to confirm the action.
If the user presses the delete button in the modal I want to send a HTTP DELETE request to an endpoint like
<code>/feedback/{id}</code>, but the ID is dynamic, as we are using one modal for all the feedbacks vs having one modal
per one feedback.</p>
<p>I am using HTMX, generated using templ and using AlpineJS for some frontend interactivity, where it doesn&rsquo;t make
sense to send a request back to the server.</p>
<h2 id="solution">Solution</h2>
<p>The solution is relatively easily in the end</p>
<p>We have a button like this which triggers the modal to open, this is rendered in templ via Go. Hence the <code>fmt.Sprintf</code>
function. Here we are setting a variable which will be used by AlpineJS.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">button</span>
</span></span><span class="line"><span class="cl">    <span class="na">class</span><span class="o">=</span><span class="s">&#34;btn btn-ghost btn-xs text-neutral&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="err">@</span><span class="na">click</span><span class="o">=</span><span class="s">{</span> <span class="na">fmt</span><span class="err">.</span><span class="na">Sprintf</span><span class="err">(&#34;</span><span class="na">currentItemId </span><span class="o">=</span> <span class="s">&#39;%s&#39;</span><span class="err">;</span> <span class="na">document</span><span class="err">.</span><span class="na">getElementById</span><span class="err">(&#39;</span><span class="na">delete-modal</span><span class="err">&#39;).</span><span class="na">showModal</span><span class="err">()&#34;,</span> <span class="na">item</span><span class="err">.</span><span class="na">ID</span><span class="err">)</span> <span class="err">}</span>
</span></span><span class="line"><span class="cl">    <span class="na">aria-label</span><span class="o">=</span><span class="s">&#34;Delete Feedback&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">i</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;text-lg hgi hgi-solid hgi-delete-02&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">i</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>In one of the parent div make sure we set the <code>x-data</code> attribute, which matches the same variable we set on the <code>@click</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;&#34;</span> <span class="na">x-data</span><span class="o">=</span><span class="s">&#34;{ currentItemId: &#39;&#39; }&#34;</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>Finally here is a simplified version of the button in the modal when pressed will send the delete request.
The key line is <code>x-effect=&quot;currentItemId;htmx.process($el)&quot;</code>, we need HTMX to reevaluate the hx-delete attribute.
Else the request will go to <code>/feedback/</code> without the ID.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">button</span>
</span></span><span class="line"><span class="cl">    <span class="na">x-bind:hx-delete</span><span class="o">=</span><span class="s">&#34;&#39;/feedback/&#39; + currentItemId&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="na">x-effect</span><span class="o">=</span><span class="s">&#34;currentItemId;htmx.process($el)&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="na">hx-target</span><span class="o">=</span><span class="s">&#34;#feedback_list&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="na">hx-swap</span><span class="o">=</span><span class="s">&#34;outerHTML&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    Delete
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>That&rsquo;s it, now you can set the endpoint dynamically, generally speaking I don&rsquo;t need to use this pattern much.
As most of the time this can be set when we render the template as they are usually static.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li>Github: <a href="https://github.com/mvolkmann/htmx-examples/blob/main/dynamic-endpoint/public/index.html">https://github.com/mvolkmann/htmx-examples/blob/main/dynamic-endpoint/public/index.html</a></li>
<li>Reddit Post: <a href="https://old.reddit.com/r/htmx/comments/1fyaw8r/htmx_and_alpinejs_dynamic_data/">https://old.reddit.com/r/htmx/comments/1fyaw8r/htmx_and_alpinejs_dynamic_data/</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL - Fix Telescope Ignoring Env Files</title>
      <link>https://haseebmajid.dev/posts/2025-04-28-til-fix-telescope-ignoring-env-files/</link>
      <pubDate>Mon, 28 Apr 2025 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2025-04-28-til-fix-telescope-ignoring-env-files/</guid>
      <description>&lt;p&gt;Recently I noticed when I searched using Telescope in Neovim, that I could see &lt;code&gt;env.local.template&lt;/code&gt; but not
&lt;code&gt;.env.local&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Where my keybinding looked like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;vim.keymap&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;set&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;n&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;lt;leader&amp;gt;ff&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;function&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;builtin.find_files&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;({&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;hidden&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;follow&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;end&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;desc&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Find all files&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This was because I was ignoring the &lt;code&gt;.env*&lt;/code&gt; paths in my gitignore, I could tell telescope to not follow the gitignore.
But I would get a lot more junk when searching in Telescope. I just wanted to see certain patterns but still
mostly respect the gitignore file. You can do the following:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Recently I noticed when I searched using Telescope in Neovim, that I could see <code>env.local.template</code> but not
<code>.env.local</code>.</p>
<p>Where my keybinding looked like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;leader&gt;ff&#34;</span><span class="p">,</span> <span class="kr">function</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">builtin.find_files</span><span class="p">({</span> <span class="n">hidden</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span> <span class="n">follow</span> <span class="o">=</span> <span class="kc">true</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span><span class="p">,</span> <span class="p">{</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Find all files&#34;</span> <span class="p">})</span>
</span></span></code></pre></div><p>This was because I was ignoring the <code>.env*</code> paths in my gitignore, I could tell telescope to not follow the gitignore.
But I would get a lot more junk when searching in Telescope. I just wanted to see certain patterns but still
mostly respect the gitignore file. You can do the following:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1">#  create a ~/.ignore file</span>
</span></span><span class="line"><span class="cl">nvim ~/.ignore
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Mine looks like this</span>
</span></span><span class="line"><span class="cl">!.env.local
</span></span><span class="line"><span class="cl">!*/.env.local
</span></span><span class="line"><span class="cl">!.env.test
</span></span><span class="line"><span class="cl">!*/.env.test
</span></span></code></pre></div><p>Now in Telescope the <code>.env.local</code> and <code>.env.test</code> files are searchable. But still ignored by git I don&rsquo;t want to commit
them as they contain secrets.</p>
<p>This of course assumes you are using ripgrep to do the search, in my config I have the following:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="n">vimgrep_arguments</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;rg&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;-L&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;--color=never&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;--no-heading&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;--with-filename&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;--line-number&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;--column&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;--smart-case&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;--fixed-strings&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span></code></pre></div><p>GitHub Issue: <a href="https://github.com/LunarVim/LunarVim/discussions/3770#discussioncomment-11523524">https://github.com/LunarVim/LunarVim/discussions/3770#discussioncomment-11523524</a></p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Voxicle Week 9 &amp; Week 10</title>
      <link>https://haseebmajid.dev/posts/2025-04-28-voxicle-week-9-week-10/</link>
      <pubDate>Mon, 28 Apr 2025 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2025-04-28-voxicle-week-9-week-10/</guid>
      <description>&lt;h3 id=&#34;last-2-week&#34;&gt;Last 2 week&lt;/h3&gt;
&lt;p&gt;Again got really busy with some personal stuff, and stuff at work so didn&amp;rsquo;t really have much motivation to work
on this project. But I have been forcing myself to code, so at least make some progeres.&lt;/p&gt;
&lt;p&gt;I did the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Worked out we can use row level security in Postgres for better authorization&lt;/li&gt;
&lt;li&gt;Added lots more tests&lt;/li&gt;
&lt;li&gt;Edit Feedback
&lt;ul&gt;
&lt;li&gt;Dynamic URL using Alpine&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Figure out duplication bug&lt;/li&gt;
&lt;li&gt;Filter on search&lt;/li&gt;
&lt;li&gt;More filter options&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;next-week&#34;&gt;Next Week&lt;/h3&gt;
&lt;p&gt;Next week I want to;&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h3 id="last-2-week">Last 2 week</h3>
<p>Again got really busy with some personal stuff, and stuff at work so didn&rsquo;t really have much motivation to work
on this project. But I have been forcing myself to code, so at least make some progeres.</p>
<p>I did the following:</p>
<ul>
<li>Worked out we can use row level security in Postgres for better authorization</li>
<li>Added lots more tests</li>
<li>Edit Feedback
<ul>
<li>Dynamic URL using Alpine</li>
</ul>
</li>
<li>Figure out duplication bug</li>
<li>Filter on search</li>
<li>More filter options</li>
</ul>
<h3 id="next-week">Next Week</h3>
<p>Next week I want to;</p>
<ul>
<li>Tidy up the UI for main feedback page</li>
<li>Better error modals</li>
<li>Implement RLS in Postgres</li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Voxicle Week 7 &amp; Week 8</title>
      <link>https://haseebmajid.dev/posts/2025-04-14-voxicle-week-7-week-8/</link>
      <pubDate>Mon, 14 Apr 2025 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2025-04-14-voxicle-week-7-week-8/</guid>
      <description>&lt;h3 id=&#34;last-2-week&#34;&gt;Last 2 week&lt;/h3&gt;
&lt;p&gt;Some stuff kept me busy so I couldn&amp;rsquo;t really work on my project work.&lt;/p&gt;
&lt;p&gt;I did the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Added tests
&lt;ul&gt;
&lt;li&gt;E2E with new skeleton&lt;/li&gt;
&lt;li&gt;How to debug E2E tests with delve (i.e. the server running separately)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Fixed a bunch of TODOs in the code base&lt;/li&gt;
&lt;li&gt;Delete feedback&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;However I got a busy with other random pieces of work that come up that kept me busy. Hopefully this week will be
more productive.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h3 id="last-2-week">Last 2 week</h3>
<p>Some stuff kept me busy so I couldn&rsquo;t really work on my project work.</p>
<p>I did the following:</p>
<ul>
<li>Added tests
<ul>
<li>E2E with new skeleton</li>
<li>How to debug E2E tests with delve (i.e. the server running separately)</li>
</ul>
</li>
<li>Fixed a bunch of TODOs in the code base</li>
<li>Delete feedback</li>
</ul>
<p>However I got a busy with other random pieces of work that come up that kept me busy. Hopefully this week will be
more productive.</p>
<h3 id="next-week">Next Week</h3>
<p>Next week I want to;</p>
<ul>
<li>Figure out duplication bug</li>
<li>Filter on search</li>
<li>More filter options</li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL - How to Get Dropbar and Auto Session to Work</title>
      <link>https://haseebmajid.dev/posts/2025-04-12-til-how-to-get-dropbar-and-auto-session-to-work/</link>
      <pubDate>Sat, 12 Apr 2025 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2025-04-12-til-how-to-get-dropbar-and-auto-session-to-work/</guid>
      <description>&lt;details
  class=&#34;notice info&#34;
  open=&#34;true&#34;
&gt;
    &lt;summary class=&#34;notice-title&#34;&gt;Original Article&lt;/summary&gt;
  
  You can find more context on the problem &lt;a href=&#34;https://haseebmajid.dev/posts/2025-03-22-til-fix-issue-with-dropbar-and-auto-session&#34;&gt;here&lt;/a&gt;
&lt;/details&gt;

&lt;p&gt;In my post last month I thought I had fixed this issue but turns out I did not. The actual fix to stop getting
this error:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;5108: Error executing lua &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;string &lt;span class=&#34;s2&#34;&gt;&amp;#34;v:lua&amp;#34;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;:1: attempt to call global &lt;span class=&#34;s1&#34;&gt;&amp;#39;dropbar&amp;#39;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;a nil value&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;stack traceback:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;string &lt;span class=&#34;s2&#34;&gt;&amp;#34;v:lua&amp;#34;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;:1: in main chunk
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;bat /home/haseeb/.local/share/nixCats-nvim/sessions/%2Fhome%2Fhaseeb%2Fprojects%2Fvoxicle.vim &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; rg dropbar
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;setlocal &lt;span class=&#34;nv&#34;&gt;winbar&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;%&lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;%v:lua.dropbar&lt;span class=&#34;o&#34;&gt;()&lt;/span&gt;%&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;setlocal &lt;span class=&#34;nv&#34;&gt;winbar&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;%&lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;%v:lua.dropbar&lt;span class=&#34;o&#34;&gt;()&lt;/span&gt;%&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;setlocal &lt;span class=&#34;nv&#34;&gt;winbar&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;%&lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;%v:lua.dropbar&lt;span class=&#34;o&#34;&gt;()&lt;/span&gt;%&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We want the winbar to nil. The fix that worked for me was this:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">Original Article</summary>
  
  You can find more context on the problem <a href="/posts/2025-03-22-til-fix-issue-with-dropbar-and-auto-session">here</a>
</details>

<p>In my post last month I thought I had fixed this issue but turns out I did not. The actual fix to stop getting
this error:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">5108: Error executing lua <span class="o">[</span>string <span class="s2">&#34;v:lua&#34;</span><span class="o">]</span>:1: attempt to call global <span class="s1">&#39;dropbar&#39;</span> <span class="o">(</span>a nil value<span class="o">)</span>
</span></span><span class="line"><span class="cl">stack traceback:
</span></span><span class="line"><span class="cl">        <span class="o">[</span>string <span class="s2">&#34;v:lua&#34;</span><span class="o">]</span>:1: in main chunk
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">bat /home/haseeb/.local/share/nixCats-nvim/sessions/%2Fhome%2Fhaseeb%2Fprojects%2Fvoxicle.vim <span class="p">|</span> rg dropbar
</span></span><span class="line"><span class="cl">setlocal <span class="nv">winbar</span><span class="o">=</span>%<span class="o">{</span>%v:lua.dropbar<span class="o">()</span>%<span class="o">}</span>
</span></span><span class="line"><span class="cl">setlocal <span class="nv">winbar</span><span class="o">=</span>%<span class="o">{</span>%v:lua.dropbar<span class="o">()</span>%<span class="o">}</span>
</span></span><span class="line"><span class="cl">setlocal <span class="nv">winbar</span><span class="o">=</span>%<span class="o">{</span>%v:lua.dropbar<span class="o">()</span>%<span class="o">}</span>
</span></span></code></pre></div><p>We want the winbar to nil. The fix that worked for me was this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="n">vim.o</span><span class="p">.</span><span class="n">sessionoptions</span> <span class="o">=</span> <span class="s2">&#34;blank,buffers,curdir,folds,help,tabpages,winsize,winpos,terminal,localoptions&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">require</span><span class="p">(</span><span class="s2">&#34;auto-session&#34;</span><span class="p">).</span><span class="n">setup</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">	<span class="n">pre_save_cmds</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="kr">function</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">			<span class="n">vim.cmd</span><span class="p">(</span><span class="s">[[
</span></span></span><span class="line"><span class="cl"><span class="s">                noautocmd windo set winbar=
</span></span></span><span class="line"><span class="cl"><span class="s">                noautocmd windo setlocal winbar=
</span></span></span><span class="line"><span class="cl"><span class="s">            ]]</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="kr">end</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span></code></pre></div>]]></content:encoded>
    </item>
    
    <item>
      <title>Voxicle Week 6</title>
      <link>https://haseebmajid.dev/posts/2025-03-31-voxicle-week-6/</link>
      <pubDate>Mon, 31 Mar 2025 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2025-03-31-voxicle-week-6/</guid>
      <description>&lt;h3 id=&#34;this-week&#34;&gt;This week&lt;/h3&gt;
&lt;p&gt;I did the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Finish core feedback page
&lt;ul&gt;
&lt;li&gt;Add new feedback&lt;/li&gt;
&lt;li&gt;upvote&lt;/li&gt;
&lt;li&gt;display as list or grid&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Stretch
&lt;ul&gt;
&lt;li&gt;try to implement basic RBAC (more of a PoC)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;However I got a busy with other random pieces of work that come up that kept me busy. Hopefully this week will be
more productive.&lt;/p&gt;
&lt;h3 id=&#34;next-week&#34;&gt;Next Week&lt;/h3&gt;
&lt;p&gt;Next week I want to;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Finish core feedback page
&lt;ul&gt;
&lt;li&gt;search&lt;/li&gt;
&lt;li&gt;display as list or grid&lt;/li&gt;
&lt;li&gt;email updates when a feature has been completed&lt;/li&gt;
&lt;li&gt;more filter options&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Stretch
&lt;ul&gt;
&lt;li&gt;public/private views&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Carry on working on the core feature set and make it so we can share with other people.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h3 id="this-week">This week</h3>
<p>I did the following:</p>
<ul>
<li>Finish core feedback page
<ul>
<li>Add new feedback</li>
<li>upvote</li>
<li>display as list or grid</li>
</ul>
</li>
<li>Stretch
<ul>
<li>try to implement basic RBAC (more of a PoC)</li>
</ul>
</li>
</ul>
<p>However I got a busy with other random pieces of work that come up that kept me busy. Hopefully this week will be
more productive.</p>
<h3 id="next-week">Next Week</h3>
<p>Next week I want to;</p>
<ul>
<li>Finish core feedback page
<ul>
<li>search</li>
<li>display as list or grid</li>
<li>email updates when a feature has been completed</li>
<li>more filter options</li>
</ul>
</li>
<li>Stretch
<ul>
<li>public/private views</li>
</ul>
</li>
</ul>
<p>Carry on working on the core feature set and make it so we can share with other people.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Voxicle Week 5</title>
      <link>https://haseebmajid.dev/posts/2025-03-24-voxicle-week-5/</link>
      <pubDate>Mon, 24 Mar 2025 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2025-03-24-voxicle-week-5/</guid>
      <description>&lt;h3 id=&#34;this-week&#34;&gt;This week&lt;/h3&gt;
&lt;p&gt;On my list of tasks I had the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fix auth refresh token flow not fully working&lt;/li&gt;
&lt;li&gt;Add span information to the auth middleware&lt;/li&gt;
&lt;li&gt;Simplify templ with pop drilling&lt;/li&gt;
&lt;li&gt;Update project name&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And managed to do all of them, even starting to work the core feedback part of the application i.e. allowing
users to actually add feedback to the project. I rebranded the app from Go Feedback to Voxicle (which means voice and cycle).&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h3 id="this-week">This week</h3>
<p>On my list of tasks I had the following:</p>
<ul>
<li>Fix auth refresh token flow not fully working</li>
<li>Add span information to the auth middleware</li>
<li>Simplify templ with pop drilling</li>
<li>Update project name</li>
</ul>
<p>And managed to do all of them, even starting to work the core feedback part of the application i.e. allowing
users to actually add feedback to the project. I rebranded the app from Go Feedback to Voxicle (which means voice and cycle).</p>
<p>Link to new URL: <a href="https://voxicle.app">https://voxicle.app</a></p>
<h3 id="next-week">Next Week</h3>
<p>Next week I want to;</p>
<ul>
<li>Finish core feedback page
<ul>
<li>Add new feedback</li>
<li>upvote</li>
<li>search</li>
<li>display as list or grid</li>
<li>email updates when a feature has been completed</li>
</ul>
</li>
<li>Stretch
<ul>
<li>try to implement basic RBAC (more of a PoC)</li>
<li>public/private views</li>
</ul>
</li>
</ul>
<p>Now onto the core features of the app, that users actually want to use. We can actually then start to ship the app
and share with some early users and get some feedback hopefully.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Fix Issue With Dropbar and Auto Session</title>
      <link>https://haseebmajid.dev/posts/2025-03-22-til-fix-issue-with-dropbar-and-auto-session/</link>
      <pubDate>Sat, 22 Mar 2025 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2025-03-22-til-fix-issue-with-dropbar-and-auto-session/</guid>
      <description>&lt;details
  class=&#34;notice danger&#34;
  open=&#34;true&#34;
&gt;
    &lt;summary class=&#34;notice-title&#34;&gt;Actual Fix&lt;/summary&gt;
  
  This fix didn&amp;rsquo;t actually work the actual fix can be found &lt;a href=&#34;https://haseebmajid.dev/posts/2025-04-12-til-how-to-get-dropbar-and-auto-session-to-work&#34;&gt;here&lt;/a&gt;
&lt;/details&gt;

&lt;p&gt;I recently moved to &lt;a href=&#34;https://github.com/Bekaboo/dropbar.nvim&#34;&gt;dropbar&lt;/a&gt; from barbecue as it has been archived and kept
getting a weird error for buffer open when I would reopen Neovim. I use the &lt;a href=&#34;https://github.com/rmagatti/auto-session&#34;&gt;auto-session plugin&lt;/a&gt;,
which loads back the previous state of NixVim when I last exited in that folder, i.e. open buffers.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;E5108&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Error&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;executing&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;lua&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;string&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;v:lua&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]:&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;attempt&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;to&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;call&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;global&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;dropbar&amp;#39;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;a&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;stack&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;traceback&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;string&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;v:lua&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]:&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;main&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;chunk&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I noticed though I would only get this error the second time I would open Neovim, which then made think maybe it was
an issue with auto-session. So I looked at the serialised file it generates so it knows what to load when we open
Neovim again. Which I found at:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<details
  class="notice danger"
  open="true"
>
    <summary class="notice-title">Actual Fix</summary>
  
  This fix didn&rsquo;t actually work the actual fix can be found <a href="/posts/2025-04-12-til-how-to-get-dropbar-and-auto-session-to-work">here</a>
</details>

<p>I recently moved to <a href="https://github.com/Bekaboo/dropbar.nvim">dropbar</a> from barbecue as it has been archived and kept
getting a weird error for buffer open when I would reopen Neovim. I use the <a href="https://github.com/rmagatti/auto-session">auto-session plugin</a>,
which loads back the previous state of NixVim when I last exited in that folder, i.e. open buffers.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="n">E5108</span><span class="p">:</span> <span class="n">Error</span> <span class="n">executing</span> <span class="n">lua</span> <span class="p">[</span><span class="n">string</span> <span class="s2">&#34;v:lua&#34;</span><span class="p">]:</span><span class="mi">1</span><span class="p">:</span> <span class="n">attempt</span> <span class="n">to</span> <span class="n">call</span> <span class="n">global</span> <span class="s1">&#39;dropbar&#39;</span> <span class="p">(</span><span class="n">a</span> <span class="kc">nil</span> <span class="n">value</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">stack</span> <span class="n">traceback</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="p">[</span><span class="n">string</span> <span class="s2">&#34;v:lua&#34;</span><span class="p">]:</span><span class="mi">1</span><span class="p">:</span> <span class="kr">in</span> <span class="n">main</span> <span class="n">chunk</span>
</span></span></code></pre></div><p>I noticed though I would only get this error the second time I would open Neovim, which then made think maybe it was
an issue with auto-session. So I looked at the serialised file it generates so it knows what to load when we open
Neovim again. Which I found at:</p>
<p><code>nvim /home/haseeb/.local/share/nvim/sessions/%2Fhome%2Fhaseeb%2Fprojects%2Fvoxicle.vim</code></p>
<p>One line in there looked like this:</p>
<p><code>setlocal winbar=%{%v:lua.dropbar()%}</code></p>
<p>So I removed this line by updating my config:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="n">require</span><span class="p">(</span><span class="s2">&#34;auto-session&#34;</span><span class="p">).</span><span class="n">setup</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">	<span class="n">pre_save_cmds</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="kr">function</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">			<span class="n">vim.opt</span><span class="p">.</span><span class="n">winbar</span> <span class="o">=</span> <span class="kc">nil</span> <span class="c1">-- Clear winbar before saving session</span>
</span></span><span class="line"><span class="cl">		<span class="kr">end</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span></code></pre></div><p>This stopped the error from occurring, as there is no winbar line now. I am not a 100% what was happening, but it seems
it was running this line before dropbar had loaded. I am using <a href="https://github.com/BirdeeHub/nixCats-nvim">nixCats</a>.
Anyway, that&rsquo;s it! That should resolve the issues.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Go Feedback Week 4</title>
      <link>https://haseebmajid.dev/posts/2025-03-17-go-feedback-week-4/</link>
      <pubDate>Mon, 17 Mar 2025 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2025-03-17-go-feedback-week-4/</guid>
      <description>&lt;h3 id=&#34;this-week&#34;&gt;This week&lt;/h3&gt;
&lt;p&gt;I decided to use wristband to the tenant and then organization on my side of the app. I am not fully happy with it.
During sign up the user creates a tenant but that might be confusing to the customer vs telling them its an organization.
But it does mean a new user is created and tenant on both wristband side and in the service database.&lt;/p&gt;
&lt;p&gt;Then I moved onto working on the subscription logic with paddle. Updated the pricing page to pull in data from the
backend when populating the page. Also integrating the overlay checkout, which is the simplest way to integrate
paddle with my app. I cannot style it as much as the inline one, but it involves writing less JS. So I will use that
for now. I am finishing off the subscription webhook logic, to create the data on our side.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h3 id="this-week">This week</h3>
<p>I decided to use wristband to the tenant and then organization on my side of the app. I am not fully happy with it.
During sign up the user creates a tenant but that might be confusing to the customer vs telling them its an organization.
But it does mean a new user is created and tenant on both wristband side and in the service database.</p>
<p>Then I moved onto working on the subscription logic with paddle. Updated the pricing page to pull in data from the
backend when populating the page. Also integrating the overlay checkout, which is the simplest way to integrate
paddle with my app. I cannot style it as much as the inline one, but it involves writing less JS. So I will use that
for now. I am finishing off the subscription webhook logic, to create the data on our side.</p>
<h3 id="next-week">Next week</h3>
<p>Next week I want to;</p>
<ul>
<li>Fix auth refresh token flow not fully working</li>
<li>Add span information to the auth middleware</li>
<li>Simplify templ with pop drilling</li>
<li>Update project name</li>
</ul>
<p>Go Feedback can be confused with the Go programming language. I spoke to the Go team and they said I could either change the mascot.
or the name. I wasn&rsquo;t super fond of the name much so will look to rebrand and update everywhere its called Go Feedback.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Go Feedback Week 3</title>
      <link>https://haseebmajid.dev/posts/2025-03-10-go-feedback-week-3/</link>
      <pubDate>Mon, 10 Mar 2025 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2025-03-10-go-feedback-week-3/</guid>
      <description>&lt;h3 id=&#34;week-3&#34;&gt;Week 3&lt;/h3&gt;
&lt;p&gt;I finished off the auth flow this week, decided to move over to using the wristband UI instead of using my own components.
It ended up being a lot of code which was nice to actually delete and I think the wristband setup is pretty nice
and slick. I had to adjust slightly how I will create users in my own DB but it wasn&amp;rsquo;t a big deal.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h3 id="week-3">Week 3</h3>
<p>I finished off the auth flow this week, decided to move over to using the wristband UI instead of using my own components.
It ended up being a lot of code which was nice to actually delete and I think the wristband setup is pretty nice
and slick. I had to adjust slightly how I will create users in my own DB but it wasn&rsquo;t a big deal.</p>
<p>I also migrated some of my UI to use the DaisyUI library, which should simplify the amount of CSS I have to manage
on my own.</p>
<p>I also started the onboarding flow, after the user has signed up where they create a new organization. Which
should be finished early next week.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Go Feedback My New Side Project</title>
      <link>https://haseebmajid.dev/posts/2025-03-03-go-feedback-my-new-side-project/</link>
      <pubDate>Mon, 03 Mar 2025 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2025-03-03-go-feedback-my-new-side-project/</guid>
      <description>&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;p&gt;Last year, I worked on two main side projects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;OptiNix: CLI tool to easily find nix options.&lt;/li&gt;
&lt;li&gt;Banter Bus: My third attempt at building a browser based multi-player game.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Whilst Banter Bus is not fully done, I got it to an almost working state and at some point will go back and fix a bunch
of the bugs and add some new features.&lt;/p&gt;
&lt;p&gt;However, I finally decided I wanted to try my hand at building something that I could try to make some money from.
Whilst also learning more about business, product design etc. How can I launch an actual product vs building something?
Purely for fun.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="background">Background</h2>
<p>Last year, I worked on two main side projects:</p>
<ul>
<li>OptiNix: CLI tool to easily find nix options.</li>
<li>Banter Bus: My third attempt at building a browser based multi-player game.</li>
</ul>
<p>Whilst Banter Bus is not fully done, I got it to an almost working state and at some point will go back and fix a bunch
of the bugs and add some new features.</p>
<p>However, I finally decided I wanted to try my hand at building something that I could try to make some money from.
Whilst also learning more about business, product design etc. How can I launch an actual product vs building something?
Purely for fun.</p>
<p>So I decided to build <a href="https://gofeedback.app">Go Feedback</a>, a tool which makes it easier to collect user feedback.
For once, it&rsquo;s also not open source, i.e. anyone can view my code (for now). Which is not something I&rsquo;ve really
done.</p>
<p>Either way, my thinking was, I wanted to build something and just learn. If nothing else, I can likely re-use the existing
code I write. Such as the auth, subscription logic or even the structure of the landing page.</p>
<p>Funnily enough, at the beginning of the year, I really wanted to dive into game dev. But gave myself one month to finish
Banter Bus (end of Jan). In that time, my passion of game dev died down, which is probably proved my heart wasn&rsquo;t in it.</p>
<p>Being a backend developer by working on this &ldquo;micro-saas&rdquo; there is a lot I can learn from building my own app vs
making a game which I cannot really incorporate in my day-to-day job.</p>
<h2 id="week-1">Week 1</h2>
<p>The first week I build out the landing page, I am great at frontend dev. I decided to use HTMX, alpinejs and tailwindcss.
I ended up using <a href="https://www.tailwindai.dev/">tailwindai</a> to help create the page. I also ended up choosing to use
a theme similar to the Daisy UI cupcake theme.</p>
<p>I put down all the big features/ideas I had that businesses might find useful and created mock-ups for them again
mostly using AI to help me. Due to inexperience in front-end development, and to be honest, currently my lack
of interest to learn it properly.</p>
<p>I added a fake door as a waitlist to gauge interest, which currently only me and my friends have used.</p>
<p><img
        loading="lazy"
        src="/posts/2025-03-03-go-feedback-my-new-side-project/images/hero.png"
        type=""
        alt="Hero Landing Page"
        
      /></p>
<h2 id="week-2">Week 2</h2>
<p>This week I spent mostly building out the auth part, i.e. login, signup and logout. I wanted to try to use the magic
link email flow. To make it as easy as possible for the user to sign up without needing a password. They just need
click a link in an email they are sent.</p>
<p>I was originally going to use <a href="https://clerk.com/">clerk</a>, but had issues (could just have been me) integrating cleanly with my backend
and ended up moving over to <a href="https://www.wristband.dev/">wristband.dev</a>. Whilst I have experience maintaining auth
services, I haven&rsquo;t really ever had to build it out myself for a web app. So it was a good learning experience for me.</p>
<p>I also learnt about other random things like CSRF and how to avoid that.</p>
<h2 id="building-in-public">Building in public</h2>
<p>I want to try to build in public, to collect feedback and to keep myself honest. It would also be a nice diary
to track my own progress. I don&rsquo;t really want to use Twitter, which lots of the &ldquo;indie hacker&rdquo; builder community
seem to use. So I am going to try to use:</p>
<ul>
<li>peerlist</li>
<li>bluesky</li>
</ul>
<p>And maintain weekly blog posts, maybe even YouTube or TikTok videos to show off the progress. Some weeks there will likely
be nothing to really show off.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Fix Firefox Rendering Emoji Font (NixOS)</title>
      <link>https://haseebmajid.dev/posts/2025-01-20-how-to-fix-firefox-rendering-emoji-font-black-bold-numbers-issue/</link>
      <pubDate>Mon, 20 Jan 2025 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2025-01-20-how-to-fix-firefox-rendering-emoji-font-black-bold-numbers-issue/</guid>
      <description>&lt;p&gt;I was some issues with the ways some fonts were rendering in Nix/NixOS machines. It seemed to specifically effect
a few apps, mainly Firefox and only certain webpages. Sometimes numbers would render like below:&lt;/p&gt;
&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2025-01-20-how-to-fix-firefox-rendering-emoji-font-black-bold-numbers-issue/images/bad.png&#34;
        type=&#34;&#34;
        alt=&#34;Bad Rendering&#34;
        
      /&gt;&lt;/p&gt;
&lt;h2 id=&#34;debugging-steps&#34;&gt;Debugging Steps&lt;/h2&gt;
&lt;p&gt;This occurred off and on over the course of a few weeks. My font setup was primarily done using
&lt;a href=&#34;https://github.com/danth/stylix&#34;&gt;stylix&lt;/a&gt; and a bit of NixOS option config.&lt;/p&gt;
&lt;p&gt;I noticed this only seemed to happen when the CSS on the page include a font-family like so &amp;ldquo;monospace&amp;rdquo; i.e.
the monospace font in quotes. But without quotes, the font would render fine.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I was some issues with the ways some fonts were rendering in Nix/NixOS machines. It seemed to specifically effect
a few apps, mainly Firefox and only certain webpages. Sometimes numbers would render like below:</p>
<p><img
        loading="lazy"
        src="/posts/2025-01-20-how-to-fix-firefox-rendering-emoji-font-black-bold-numbers-issue/images/bad.png"
        type=""
        alt="Bad Rendering"
        
      /></p>
<h2 id="debugging-steps">Debugging Steps</h2>
<p>This occurred off and on over the course of a few weeks. My font setup was primarily done using
<a href="https://github.com/danth/stylix">stylix</a> and a bit of NixOS option config.</p>
<p>I noticed this only seemed to happen when the CSS on the page include a font-family like so &ldquo;monospace&rdquo; i.e.
the monospace font in quotes. But without quotes, the font would render fine.</p>
<p>I made a few posts to ask for help, as I was confused, why this was happening, and found a similar issue,
you can find here: <a href="https://old.reddit.com/r/NixOS/comments/1fha5hl/firefox_font_problem/">https://old.reddit.com/r/NixOS/comments/1fha5hl/firefox_font_problem/</a>.</p>
<p>Anyway, I didn&rsquo;t get any reply, so I decided to use fc-match to see what font my system was returning.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">blog on  2025-01-20-how-to-fix-firefox-rendering-emoji-font-black-bold-numbers-issue <span class="o">[</span><span class="nv">$?</span><span class="o">]</span> via 🐹 v1.22.10 via ❄  impure <span class="o">(</span>nix-shell-env<span class="o">)</span> took 19s
</span></span><span class="line"><span class="cl">❯ fc-match monospace
</span></span><span class="line"><span class="cl">MonoLisa-Regular.ttf: <span class="s2">&#34;MonoLisa&#34;</span> <span class="s2">&#34;Regular&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">blog on  2025-01-20-how-to-fix-firefox-rendering-emoji-font-black-bold-numbers-issue <span class="o">[</span><span class="nv">$?</span><span class="o">]</span> via 🐹 v1.22.10 via ❄  impure <span class="o">(</span>nix-shell-env<span class="o">)</span>
</span></span><span class="line"><span class="cl">❯ fc-match <span class="s1">&#39;&#34;monospace&#34;&#39;</span>
</span></span><span class="line"><span class="cl">NotoSans<span class="o">[</span>wdth,wght<span class="o">]</span>.ttf: <span class="s2">&#34;Noto Sans&#34;</span> <span class="s2">&#34;Regular&#34;</span>
</span></span></code></pre></div><p>I thought I&rsquo;d found the smoking gun, ohh it was trying to use the San font to render it. I asked someone to share their
working config from an Ubuntu machine and theirs also seemed to be similar to mine.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">╰─ fc-match <span class="s2">&#34;monospace&#34;</span>
</span></span><span class="line"><span class="cl">DejaVuSansMono.ttf: <span class="s2">&#34;DejaVu Sans Mono&#34;</span> <span class="s2">&#34;Book&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">╰─ fc-match <span class="s1">&#39;&#34;monospace&#34;&#39;</span>
</span></span><span class="line"><span class="cl">DejaVuSans.ttf: <span class="s2">&#34;DejaVu Sans&#34;</span> <span class="s2">&#34;Book&#34;</span>
</span></span></code></pre></div><p>Then whilst looking up stuff specific to how Firefox renders fonts, I realised in the DevTools there is a page for
fonts, it&rsquo;s just a bit hidden. I mean, it makes sense, of course there is.</p>
<p><img
        loading="lazy"
        src="/posts/2025-01-20-how-to-fix-firefox-rendering-emoji-font-black-bold-numbers-issue/images/fonts.png"
        type=""
        alt="Font Panel Firefox"
        
      /></p>
<p>So looking at this I realised it wasn&rsquo;t bold text it was rendering, but it was falling back and rendering the numbers
as emojis.</p>
<p>I then had a closer look at my font config in Nix and realised it wasn&rsquo;t doing what I thought it was doing.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="cp">&lt;?xml version=&#34;1.0&#34;?&gt;</span>
</span></span><span class="line"><span class="cl"><span class="cp">&lt;!DOCTYPE fontconfig SYSTEM &#34;fonts.dtd&#34;&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;fontconfig&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;alias</span> <span class="na">binding=</span><span class="s">&#34;weak&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;family&gt;</span>monospace<span class="nt">&lt;/family&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;prefer&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&lt;family&gt;</span>emoji<span class="nt">&lt;/family&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;/prefer&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/alias&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;alias</span> <span class="na">binding=</span><span class="s">&#34;weak&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;family&gt;</span>sans-serif<span class="nt">&lt;/family&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;prefer&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&lt;family&gt;</span>emoji<span class="nt">&lt;/family&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;/prefer&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/alias&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;alias</span> <span class="na">binding=</span><span class="s">&#34;weak&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;family&gt;</span>serif<span class="nt">&lt;/family&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;prefer&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&lt;family&gt;</span>emoji<span class="nt">&lt;/family&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;/prefer&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/alias&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/fontconfig&gt;</span>
</span></span></code></pre></div><p>What I had done by copying and pasting some config I didn&rsquo;t fully understand (tut tut tut) is if the character was
available in the emoji font use that instead, i.e. the numbers or certain symbols like TM.
So after deleting that config and reloading Firefox, it was rendering properly as expected.</p>
<p>That&rsquo;s it! As expected, remember, to make sure you understand what your code is doing. I thought it was allowing emojis
to be rendered with the monospace font, not replacing it or preferring it.</p>
<p>Relevant commit resolving the above issue: <a href="https://gitlab.com/hmajid2301/nixicle/-/commit/3168db8df339d79e838c98f8675eb21a23452d4e">https://gitlab.com/hmajid2301/nixicle/-/commit/3168db8df339d79e838c98f8675eb21a23452d4e</a></p>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Fix PipeWire/WirePlumber Issues on NixOS</title>
      <link>https://haseebmajid.dev/posts/2025-01-15-til-how-to-fix-pipewire-wireplumber-issues-on-nixos/</link>
      <pubDate>Wed, 15 Jan 2025 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2025-01-15-til-how-to-fix-pipewire-wireplumber-issues-on-nixos/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Fix PipeWire/WirePlumber Issues on NixOS&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For the last month or so, the audio on my Desktop was broken in the sense I had to run these three commands to make it work&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;pipewire &lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;wireplumber &lt;span class=&#34;p&#34;&gt;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;pipewire-pulse
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, normally I hibernate my computer, so I didn&amp;rsquo;t really have to do this more than once or twice. However, it was very
annoying either way to remember to know how to do it.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Fix PipeWire/WirePlumber Issues on NixOS</strong></p>
<p>For the last month or so, the audio on my Desktop was broken in the sense I had to run these three commands to make it work</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pipewire <span class="p">&amp;</span>
</span></span><span class="line"><span class="cl">wireplumber <span class="p">&amp;</span>
</span></span><span class="line"><span class="cl">pipewire-pulse
</span></span></code></pre></div><p>Now, normally I hibernate my computer, so I didn&rsquo;t really have to do this more than once or twice. However, it was very
annoying either way to remember to know how to do it.</p>
<p>My nix config looked like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">services</span><span class="o">.</span><span class="n">pulseaudio</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">security</span><span class="o">.</span><span class="n">rtkit</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">services</span><span class="o">.</span><span class="n">pipewire</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">alsa</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">alsa</span><span class="o">.</span><span class="n">support32Bit</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">pulse</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">wireplumber</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">jack</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>I did notice this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ eza -al /etc/systemd/system/wireplumber.service
</span></span><span class="line"><span class="cl">lrwxrwxrwx - root  <span class="m">1</span> Jan  <span class="m">1970</span> /etc/systemd/system/wireplumber.service -&gt; /dev/null
</span></span></code></pre></div><p>But this seemed to be a red herring.</p>
<p>Which seemed to match what was in the NixOS wiki(s). Then after a bit of help from ChatGPT, it suggested taking a look
at the systemd configs in my config folder.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ ls -l ~/.config/systemd/user/
</span></span><span class="line"><span class="cl">lrwxrwxrwx - haseeb  <span class="m">8</span> Aug  <span class="m">2024</span> pipewire-pulse.service -&gt; /nix/store/dhn51w2km4fyf9ivi00rz03qs8q4mpng-pipewire-1.2.1/share/systemd/user/pipewire-pulse.service
</span></span><span class="line"><span class="cl">lrwxrwxrwx - haseeb  <span class="m">8</span> Aug  <span class="m">2024</span> pipewire-pulse.socket -&gt; /nix/store/dhn51w2km4fyf9ivi00rz03qs8q4mpng-pipewire-1.2.1/share/systemd/user/pipewire-pulse.socket
</span></span><span class="line"><span class="cl">lrwxrwxrwx - haseeb  <span class="m">8</span> Aug  <span class="m">2024</span> pipewire-session-manager.service -&gt; /home/haseeb/.config/systemd/user/wireplumber.service
</span></span><span class="line"><span class="cl">lrwxrwxrwx - haseeb  <span class="m">8</span> Aug  <span class="m">2024</span> pipewire.service -&gt; /nix/store/dhn51w2km4fyf9ivi00rz03qs8q4mpng-pipewire-1.2.1/share/systemd/user/pipewire.service
</span></span><span class="line"><span class="cl">drwxr-xr-x - haseeb  <span class="m">8</span> Aug  <span class="m">2024</span> pipewire.service.wants
</span></span><span class="line"><span class="cl">lrwxrwxrwx - haseeb  <span class="m">8</span> Aug  <span class="m">2024</span> pipewire.socket -&gt; /nix/store/dhn51w2km4fyf9ivi00rz03qs8q4mpng-pipewire-1.2.1/share/systemd/user/pipewire.socket
</span></span><span class="line"><span class="cl">lrwxrwxrwx - haseeb  <span class="m">8</span> Aug  <span class="m">2024</span> wireplumber.service -&gt; /nix/store/36h5pwq11jj1pzf6hgbwnfvk9xj4my2p-wireplumber-0.5.5/share/systemd/user/wireplumber.service
</span></span></code></pre></div><p>You cannot see it at here but the symlinks themselves were broken, so after deleting all of them manually (they were red
in my terminal):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">trash ~/.config/systemd/user/pipewire-session-manager.service
</span></span></code></pre></div><p>I re-ran my Nix rebuild config command <code>nh os switch</code>.</p>
<p>Then enabled them like so <code>systemctl enable --user wireplumber.service</code> and restarted a few as they failed the first
time due to missing a file which, I assume, was created later, and now my audio works without me needing to do anything
manually. Which is the way it should&rsquo;ve always been, but alas, that&rsquo;s what happens when you are tweaking your setup.</p>
<p>That&rsquo;s It! I hoped it helped.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>My Year in Review 2024</title>
      <link>https://haseebmajid.dev/posts/2025-01-01-my-year-in-review-2024/</link>
      <pubDate>Thu, 02 Jan 2025 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2025-01-01-my-year-in-review-2024/</guid>
      <description>&lt;p&gt;I did one of these last year, and thought why not do another quick year in review. Catch people up with what I&amp;rsquo;ve been
up to. This year was not nearly as transformative as my previous year in terms of changes I made to my workflow.&lt;/p&gt;
&lt;p&gt;I am still using Nix and NixOS, I am still using Neovim. I moved onto from tmux to zellij mainly because I just like
the floating terminal it provides. But other than that, it&amp;rsquo;s basically the same as the end of last year.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I did one of these last year, and thought why not do another quick year in review. Catch people up with what I&rsquo;ve been
up to. This year was not nearly as transformative as my previous year in terms of changes I made to my workflow.</p>
<p>I am still using Nix and NixOS, I am still using Neovim. I moved onto from tmux to zellij mainly because I just like
the floating terminal it provides. But other than that, it&rsquo;s basically the same as the end of last year.</p>
<h2 id="split-keyboard">Split Keyboard</h2>
<p><img
        loading="lazy"
        src="/posts/2025-01-01-my-year-in-review-2024/images/voyager.png"
        type=""
        alt="Voyager"
        
      /></p>
<p>This time last year, I had just bought a split keyboard. Now after using it for a year, I am typing even quicker than
I was with a normal keyboarding on my monkey type setting. I was getting about 65 WPM now I am at 75 WPM.</p>
<p>For me, it&rsquo;s been really great, in particular around accessing those difficult to reach symbols we need for programming or vim.
Think shift, alt, ^ etc. If you can afford to get one I would give it a try.
Also, as an aside, people think it looks &ldquo;cool&rdquo; and you&rsquo;ll get many questions at work.</p>
<h2 id="optinix">OptiNix</h2>
<p><img
        loading="lazy"
        src="/posts/2025-01-01-my-year-in-review-2024/images/optinix_demo.gif"
        type=""
        alt="OptiNix Demo"
        
      /></p>
<p>I finish my CLI tool <a href="https://gitlab.com/hmajid2301/optinix">OptiNix</a>. Which was one made to practice some more Golang
in my spare time, and build a tool I could actually use. Find Nix options from the CLI. There are a few issues
people have raised with it, which I need to go back and tidy up. But it was a good side project which probably
dragged on for too long.</p>
<p>It was nice to build a CLI, which I&rsquo;ve never done before and the bubble tea library made it super easy to create
an easy to use (T)UI. So I could focus on writing the business logic and not worry how to present my app on the
terminal. As I say there are plenty of optimisations I can still make and should try later this year to tidy it up.</p>
<h2 id="home-lab">Home Lab</h2>
<p>I also got way more into home labbing, and self-hosting lots of my own services. The two most useful being</p>
<ul>
<li>GitLab Runner (Especially 400 minute limit now)</li>
<li>Jellyfin Media Server (used by family and friends)
<ul>
<li>Identity management; managed by Authentik</li>
<li>Setup to use a local NAS</li>
</ul>
</li>
<li>k3s
<ul>
<li>Kubernetes cluster for learning</li>
<li>deployed banter bus my side project here</li>
</ul>
</li>
</ul>
<p>It&rsquo;s been a great way to learn more about infrastructure, and in particular about Kubernetes and flux CD (git ops).
I have deployed my own side project, Banter Bus, there as well. Alongside looking how the flux image automation works.
To automatically pick up changes for banter bus.</p>
<p>Most recently, I have been looking at learning more about monitoring with Prometheus, Grafana stack and also otel.
Exporting metrics, traces from my services but also pulling metrics from the pods themselves.</p>
<p>One big change I did make was trying to manage the entire thing with k3s and moving to managing apps on bare metal
with NixOS, where possible. Also keeping key stateful infrastructure like Redis and Postgres running on the bare metal
rather than Kubernetes. Which it wasn&rsquo;t really intended to (easily), I know there are ways you can do it.</p>
<p>But essentially, my main issue was how much config I needed to just deploy any app. Where in NixOS it was often maybe
100 lines of code. You can see my NixOS home lab config <a href="https://gitlab.com/hmajid2301/nixicle/-/blob/main/systems/x86_64-linux/ms01/default.nix?ref_type=heads">here</a> and k3s config <a href="https://gitlab.com/hmajid2301/k3s-config">here</a>.</p>
<p>Currently, I am only running it all on a single server basically and a few key apps on a tiny s100 from miniforum.
Low powered PC. Which is always on and runs apps like:</p>
<ul>
<li>Gotify for notifications</li>
<li>Uptime Kuma for tracking the uptime of my apps</li>
<li>Home Assistant mainly to turn off and on my home lab, so I don&rsquo;t waste electricity</li>
</ul>
<p>And finally, I have a small VPS on hetzner to make it easier for other people to access Jellyfin and I don&rsquo;t
have issues streaming video over say a Cloudflare tunnel.</p>
<p>In the new year, I want to consolidate my learning into a nice document, maybe a material mkdocs site.
Though, the boundary of what goes there vs my second brain vs my blog is always a bit confusing personally for myself.
In theory, I could make a blog post for everything I learn, currently it&rsquo;s a messy document in the nixicle repo
above. At least something down on paper.</p>
<h2 id="banter-bus">Banter Bus</h2>
<p><img
        loading="lazy"
        src="/posts/2025-01-01-my-year-in-review-2024/images/banterbus.png"
        type=""
        alt="Banter Bus"
        
      /></p>
<p>Main big main side project of the year, I started YouTube series about the development of this web app. Which I
Unfortunately, didn&rsquo;t really keep up, but will kick-start in the new year. But essentially this is my 3rd crack at
making a multiplayer browser game. Similar to say Jack Box, or Skribl.io etc.</p>
<p>I have got a lot further with this version because one I have my learnings from the previous versions and also
The main part I would lose motivation is writing frontend code, especially JS/TS. This time I&rsquo;ve tried to keep it to
a minimum. Basic stack for the frontend, using HTMX, Alpine where I need interactivity and tailwind CSS.
The HTML is returned from the server and generate using templ.</p>
<p>For a backend developer like myself this really simplifies everything and makes it much easier for me to focus on the
business logic and state managed almost entirely in the backend and I don&rsquo;t have to worry about how to do that in the
frontend, which I don&rsquo;t have much experience doing. Note this is my own opinion, not saying this is the only way to
create web apps. I just noticed I would often lose motivation in my own side projects when I had to work on the
frontend.</p>
<p>Which is the part you sometimes need to make neat and tidy to convince people to use your app. Anyway I&rsquo;ve made some
good progress this year. I wanted to try to get something out by the end of the year, didn&rsquo;t manage. I kept getting
distracted by random things like improving CI, learning more about Kubernetes deployment, telemetry (otel) and other
little things. I am hoping to have a mostly working version by the end of January which I can release and share with
friends and family.</p>
<p>I think it would really be cool to have a multiplayer game that I made that I play with my friends. Overall, I have
a lot of fun making. The main thing I want to focus on is working on the unhappy path side of the code, as it&rsquo;s pretty
fragile at the moment.</p>
<ul>
<li>Link to repo <a href="https://gitlab.com/hmajid2301/banterbus">here</a></li>
<li>Link to app <a href="https://banterbus.games/">here</a></li>
</ul>
<h2 id="conference-talks">Conference Talks</h2>
<p><img
        loading="lazy"
        src="/posts/2025-01-01-my-year-in-review-2024/images/conference.jpeg"
        type=""
        alt="Conference"
        
      /></p>
<p>This year I was lucky enough to do two conferences talks, basically the same talk about using Nix to create
development environments in <a href="https://www.youtube.com/watch?v=bdGfn_ihHOk&amp;list=PLSCmmmcxRB6DilKhSz09JL9F4CVl7Vyd3&amp;index=4">Go</a>.</p>
<p>I wrote some <a href="/posts/2024-12-15-speaking-tips-i-ve-learnt/">tips here</a> that I&rsquo;ve learnt speaking at conferences
and other people have told me.</p>
<p>But essentially, it&rsquo;s a great way to get a free ticket to conference. Which is a great way to network with people, you&rsquo;d
never normally interact with. But more importantly, a great way I found to force me to learn a topic way deeper
than I normally would. You know what they say, the best way to learn something deeply is to teach it to others. If you
can explain it clearly, it shows you must know the topic pretty well.</p>
<h2 id="youtube">YouTube</h2>
<p>Link to <a href="https://www.youtube.com/@Majiy00">my channel</a></p>
<p>I also started a YouTube channel this year, it started off well, but my last few videos didn&rsquo;t get very many views.
I want to keep up the YouTube channel but focusing on really improving the quality of my videos and finding my
own style of video. The main thing I would like to improve is the audio quality. I think there are many things I&rsquo;ve
learnt making Banter Bus and would love to share (a few videos I have already done), to do give people ideas and show
how I set up things like:</p>
<ul>
<li>templ, templating</li>
<li>i18n</li>
<li>Kubernetes deployments</li>
<li>flux image automation</li>
</ul>
<p>But currently I am losing motivation towards the end of the year, didn&rsquo;t really keep up the dev vlogs for Banter Bus
like I wanted to. I need to pivot a bit in the channel.</p>
<p>I think, similarly to this blog, work out what do I want from it. How can I better advertise the channel, without being?
Obnoxious.</p>
<h2 id="neovim-back-to-lua">Neovim back to Lua</h2>
<p>As a smaller point, I was managing my nvim configuration in Nix using <a href="https://github.com/nix-community/nixvim">NixVim</a>, which is a great project if you want
just manage everything in Nix. I think towards the end of the year I decided for config that&rsquo;s very complicated i.e.
many files, I would prefer to manage it in the native language, i.e. Lua.</p>
<p>So I swapped my config over to use <a href="https://github.com/BirdeeHub/nixCats-nvim">nixcats</a>. Which allow us to do
this. Still manage dep using Nix but config via Lua, while still having the ability to pass variables from Nix to Lua.</p>
<p>I found learning the abstraction on top Lua, i.e. the nix, way annoying. I think it&rsquo;s fine for simple config. But
my Neovim config being multiple files and having Lua stringified, losing out on the LSP. I decided to have a crack
at keeping in Lua and would make it easier to follow tutorials. I could also do lazy loading, which I&rsquo;m not sure you
can do it in NixVim.</p>
<h2 id="aims-for-this-year">Aims for this year</h2>
<ul>
<li>Release a working version of Banter Bus by end of January</li>
<li>Try to release say 20ish YouTube videos next year
<ul>
<li>Find my own styles</li>
<li>With excellent audio quality
<ul>
<li>Fewer umms and arr</li>
</ul>
</li>
<li>Useful and catchy titles</li>
<li>Advertise the channel to improve get more views
<ul>
<li>I would to average say 1k views per video</li>
</ul>
</li>
</ul>
</li>
<li>Look at making some money from my side projects
<ul>
<li>I was thinking about Banter Bus, but I don&rsquo;t really know how
<ul>
<li>Besides maybe a donate button</li>
<li>Again, I want to provide a high value for the customer</li>
</ul>
</li>
<li>If it was easy, everyone would do it right?</li>
</ul>
</li>
</ul>
<p>P.S. I also got around to finally playing baldurs gate and finally completing it. I didn&rsquo;t really enjoy the combat
the first time around. Even though I was such a big of Larian&rsquo;s previous game divinity original sin 2. But eventually
I gave it another go and had a lot more fun with it.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Part 7: Zellij as Part of Your Development Workflow</title>
      <link>https://haseebmajid.dev/posts/2024-12-18-part-7-zellij-as-part-of-your-development-workflow/</link>
      <pubDate>Wed, 18 Dec 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-12-18-part-7-zellij-as-part-of-your-development-workflow/</guid>
      <description>&lt;p&gt;After another 5 months I&amp;rsquo;m finally back blogging about my development setup. Which since my last post, has been
a lot more stable. As much closer to a workflow I am mostly happy with. In this post, we will go over how I integrate
&lt;a href=&#34;https://github.com/zellij-org/zellij&#34;&gt;Zellij&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;what-is-a-terminal-multiplexer&#34;&gt;What is a terminal multiplexer?&lt;/h2&gt;
&lt;p&gt;Zellij is a terminal multiplexer think like tmux or screen. It another one of those projects written in rust.
Which seems pretty popular for creating similar tools to existing ones but usually faster.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>After another 5 months I&rsquo;m finally back blogging about my development setup. Which since my last post, has been
a lot more stable. As much closer to a workflow I am mostly happy with. In this post, we will go over how I integrate
<a href="https://github.com/zellij-org/zellij">Zellij</a>.</p>
<h2 id="what-is-a-terminal-multiplexer">What is a terminal multiplexer?</h2>
<p>Zellij is a terminal multiplexer think like tmux or screen. It another one of those projects written in rust.
Which seems pretty popular for creating similar tools to existing ones but usually faster.</p>
<p>It allows us to split up our terminal into multiple panes or sections, then navigate between them. Now if you are using
a tiling window manager you could also just create 4 terminals. But one other nice feature that tmux and Zellij let
you do it they have a server client architecture so you can close your terminal connecting to a session. Then
open a new one say an hour later and connect to your session, and it will be in the same state you left it.
This also works well if you accidentally closed your terminal that was doing some work.</p>
<p>These tools also allow us to have tabs that we can navigate between. In the image below you can see a Zellij
session which has two tabs and the current tab is split into two panes horizontally.</p>
<p><img
        loading="lazy"
        src="/posts/2024-12-18-part-7-zellij-as-part-of-your-development-workflow/images/zellij-example.png"
        type=""
        alt="Example"
        
      /></p>
<h2 id="why-zellij">Why Zellij?</h2>
<p>I was using tmux until earlier this year. The main reason I moved to Zellij was basically one main feature, floating panes.
I can press a key combination and then voilà get a floating terminal I can run commands in like, linting my code or
running tests and then when it&rsquo;s done, I can hide it.</p>
<p>Now you can do this in tmux with plugins, though I couldn&rsquo;t make it work (<a href="https://github.com/omerxx/tmux-floax">floax</a>).
You can also do this within Neovim, but the nice thing about Zellij is it stays for the duration that the tab
you are in the open. Some terminals also provide the ability to split into panes and tabs, i.e. Wezterm.</p>
<p>There are two main reasons I want to use Zellij; the floating panes and the client server architecture. Which makes it
much easier for me to jump between my projects.</p>
<h2 id="how-i-configured-it">How I configured it?</h2>
<p>As will be no surprise to anyone following me, I have set it up using Nix;
You can find my <a href="https://gitlab.com/hmajid2301/nixicle/-/blob/9d9d50ea3d8c5746bfb3f53fdfa46b8fe1d09106/modules/home/cli/multiplexers/zellij/default.nix">full config here</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">pkgs</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">lib</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">config</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}:</span>
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="n">lib</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="n">lib</span><span class="o">.</span><span class="n">nixicle</span><span class="p">;</span> <span class="k">let</span>
</span></span><span class="line"><span class="cl">  <span class="n">cfg</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">cli</span><span class="o">.</span><span class="n">multiplexers</span><span class="o">.</span><span class="n">zellij</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">inherit</span> <span class="p">(</span><span class="n">config</span><span class="o">.</span><span class="n">lib</span><span class="o">.</span><span class="n">stylix</span><span class="p">)</span> <span class="n">colors</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">sesh</span> <span class="o">=</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">writeScriptBin</span> <span class="s2">&#34;sesh&#34;</span> <span class="s1">&#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">    // ...
</span></span></span><span class="line"><span class="cl"><span class="s1">  &#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">in</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">options</span><span class="o">.</span><span class="n">cli</span><span class="o">.</span><span class="n">multiplexers</span><span class="o">.</span><span class="n">zellij</span> <span class="o">=</span> <span class="k">with</span> <span class="n">types</span><span class="p">;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="n">mkBoolOpt</span> <span class="no">false</span> <span class="s2">&#34;enable zellij multiplexer&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">config</span> <span class="o">=</span> <span class="n">mkIf</span> <span class="n">cfg</span><span class="o">.</span><span class="n">enable</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">home</span><span class="o">.</span><span class="n">packages</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="n">sesh</span>
</span></span><span class="line"><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">xdg</span><span class="o">.</span><span class="n">configFile</span><span class="o">.</span><span class="s2">&#34;zellij/config.kdl&#34;</span><span class="o">.</span><span class="n">source</span> <span class="o">=</span> <span class="sr">./config.kdl</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">xdg</span><span class="o">.</span><span class="n">configFile</span><span class="o">.</span><span class="s2">&#34;zellij/layouts/default.kdl&#34;</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="s1">&#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">        // ...
</span></span></span><span class="line"><span class="cl"><span class="s1">    &#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">programs</span><span class="o">.</span><span class="n">zellij</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>I&rsquo;ve removed some parts of my config to make it easier to read. But essentially, we set up some config files and enable
Zellij. I also have a custom script sesh which allows me to jump between my different projects. Easily!
Where I basically count as a folder/git repo as a project.</p>
<h3 id="sesh">Sesh</h3>
<p>As I said, I have a script called <code>sesh</code>, which I use to open a folder in a Zellij session. It combines
Zoxide (better cd tool) to fuzzy find (zoxide uses FZF I believe) any directory we have previously navigated to with
Zoxide.</p>
<p>Once we pick a folder we will load into a Zellij session.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl">  <span class="n">sesh</span> <span class="err">=</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">writeScriptBin</span> <span class="s2">&#34;sesh&#34;</span> <span class="s1">&#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">    #! /usr/bin/env sh
</span></span></span><span class="line"><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="cl"><span class="s1">    # Taken from https://github.com/zellij-org/zellij/issues/884#issuecomment-1851136980
</span></span></span><span class="line"><span class="cl"><span class="s1">    # select a directory using zoxide
</span></span></span><span class="line"><span class="cl"><span class="s1">    ZOXIDE_RESULT=$(zoxide query --interactive)
</span></span></span><span class="line"><span class="cl"><span class="s1">    # checks whether a directory has been selected
</span></span></span><span class="line"><span class="cl"><span class="s1">    if [[ -z &#34;$ZOXIDE_RESULT&#34; ]]; then
</span></span></span><span class="line"><span class="cl"><span class="s1">    	# if there was no directory, select returns without executing
</span></span></span><span class="line"><span class="cl"><span class="s1">    	exit 0
</span></span></span><span class="line"><span class="cl"><span class="s1">    fi
</span></span></span><span class="line"><span class="cl"><span class="s1">    # extracts the directory name from the absolute path
</span></span></span><span class="line"><span class="cl"><span class="s1">    SESSION_TITLE=$(echo &#34;$ZOXIDE_RESULT&#34; | sed &#39;s#.*/##&#39;)
</span></span></span><span class="line"><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="cl"><span class="s1">    # get the list of sessions
</span></span></span><span class="line"><span class="cl"><span class="s1">    SESSION_LIST=$(zellij list-sessions -n | awk &#39;{print $1}&#39;)
</span></span></span><span class="line"><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="cl"><span class="s1">    # checks if SESSION_TITLE is in the session list
</span></span></span><span class="line"><span class="cl"><span class="s1">    if echo &#34;$SESSION_LIST&#34; | grep -q &#34;^$SESSION_TITLE$&#34;; then
</span></span></span><span class="line"><span class="cl"><span class="s1">    	# if so, attach to existing session
</span></span></span><span class="line"><span class="cl"><span class="s1">    	zellij attach &#34;$SESSION_TITLE&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">    else
</span></span></span><span class="line"><span class="cl"><span class="s1">    	# if not, create a new session
</span></span></span><span class="line"><span class="cl"><span class="s1">    	echo &#34;Creating new session $SESSION_TITLE and CD $ZOXIDE_RESULT&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">    	cd $ZOXIDE_RESULT
</span></span></span><span class="line"><span class="cl"><span class="s1">    	zellij attach -c &#34;$SESSION_TITLE&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">    fi
</span></span></span><span class="line"><span class="cl"><span class="s1">  &#39;&#39;</span><span class="p">;</span>
</span></span></code></pre></div><p>This is not as flexible as the session switcher I had in tmux as I cannot swap using the same sesh script.
I need to use the built-in session switcher from Zellij. I don&rsquo;t really like the asymmetry, but it works fine.
Furthermore, I usually end up opening a new terminal and run sesh there, rather than remembering my key bind in zellij
swap sessions.</p>
<p>Here you can see the script, showing us the FZF fuzzy searcher alongside the Zoxide results.</p>
<p><img
        loading="lazy"
        src="/posts/2024-12-18-part-7-zellij-as-part-of-your-development-workflow/images/sesh.png"
        type=""
        alt="Example Sesh"
        
      /></p>
<h3 id="key-bindings">Key Bindings</h3>
<p>Another problem Zellij faces is the default key binding use <code>Ctrl</code> a lot and clash with my Neovim bindings.
So I ended up swapping the main key to be Alt, which doesn&rsquo;t seem to clash with anything else in my system.</p>
<p>Again, not quite as convenient as what I had in tmux, but it is mostly good enough, and I&rsquo;ve built enough muscle memory.
To achieve this, I updated my <code>config.kdl</code> file such that it looks this (I&rsquo;m pretty sure I just copied someone else&rsquo;s but
cannot find who, sorry helpful person.).</p>
<p>Zellij is modal, so we have different modes, i.e. pane mode, tab mode, search mode.</p>
<p>Find my <a href="https://gitlab.com/hmajid2301/nixicle/-/blob/9d9d50ea3d8c5746bfb3f53fdfa46b8fe1d09106/modules/home/cli/multiplexers/zellij/config.kdl">full <code>config.kdl</code> here</a>.</p>
<pre tabindex="0"><code class="language-kdl" data-lang="kdl">theme &#34;catppuccin-mocha&#34;
pane_frames false
simplified_ui true
default_shell &#34;fish&#34;
copy_on_select true

// If you&#39;d like to override the default keybindings completely, be sure to change &#34;keybinds&#34; to &#34;keybinds clear-defaults=true&#34;
keybinds {
    unbind &#34;Ctrl q&#34;
    normal {
        // uncomment this and adjust key if using copy_on_select=false
        // bind &#34;Alt c&#34; { Copy; }
    }
    locked clear-defaults=true {
        bind &#34;Alt u&#34; { SwitchToMode &#34;Normal&#34;; }
    }

    resize clear-defaults=true {
        bind &#34;Alt r&#34; &#34;Esc&#34; &#34;Ctrl {&#34; { SwitchToMode &#34;Normal&#34;; }

        bind &#34;h&#34; &#34;Left&#34; { Resize &#34;Increase Left&#34;; }
        bind &#34;j&#34; &#34;Down&#34; { Resize &#34;Increase Down&#34;; }
        bind &#34;k&#34; &#34;Up&#34; { Resize &#34;Increase Up&#34;; }
        bind &#34;l&#34; &#34;Right&#34; { Resize &#34;Increase Right&#34;; }
        bind &#34;H&#34; { Resize &#34;Decrease Left&#34;; }
        bind &#34;J&#34; { Resize &#34;Decrease Down&#34;; }
        bind &#34;K&#34; { Resize &#34;Decrease Up&#34;; }
        bind &#34;L&#34; { Resize &#34;Decrease Right&#34;; }
        bind &#34;=&#34; &#34;+&#34; { Resize &#34;Increase&#34;; }
        bind &#34;-&#34; { Resize &#34;Decrease&#34;; }
    }

    // ...
}
</code></pre><p>So I use <code>alt + t</code> for example to go into tab mode, and then we could press <code>n</code> to create a new tab.
Equally, <code>alt + p</code> then <code>-</code> will create a horizontal pane split.</p>
<p>One other super useful key binding I use a lot is <code>alt + f</code> and <code>e</code> which opens the panes contents in my editor i.e.
Neovim. Which I can then use to copy data, open links etc. Again there are tmux plugins which let use navigate the
pane itself, but I like that I can just use Neovim as I normally would.</p>
<h3 id="neovim">Neovim</h3>
<p>In tmux, I had a plugin which allowed me to navigate panes using CTRL, but I haven&rsquo;t setup anything similar in zellij.
For now, I am happy navigating using Alt between the panes whenever I need to, that which is not frequent.</p>
<h3 id="status-bar">Status Bar</h3>
<p>You can find some nice examples <a href="https://github.com/dj95/zjstatus/discussions/44">here</a> for some inspiration of possible
status bars.</p>
<p>The final part of the config is using the <a href="https://github.com/dj95/zjstatus">zellij status bar plugin</a>.
Which I wanted to look more like tmux catppuccin theme and that basically what this does. We do some string
interpolation with Nix and stylix for the base16 colours. So I could change them in one place and it would do it
across my config.</p>
<pre tabindex="0"><code class="language-kdl" data-lang="kdl">  default_tab_template {
      pane size=2 borderless=true {
          plugin location=&#34;file://${pkgs.zjstatus}/bin/zjstatus.wasm&#34; {
              format_left   &#34;{mode}#[bg=#${colors.base00}] {tabs}&#34;
              format_center &#34;&#34;
              format_right  &#34;#[bg=#${colors.base00},fg=#${colors.base0D}]#[bg=#${colors.base0D},fg=#${colors.base01},bold] #[bg=#${colors.base02},fg=#${colors.base05},bold] {session} #[bg=#${colors.base03},fg=#${colors.base05},bold]&#34;
              format_space  &#34;&#34;
              format_hide_on_overlength &#34;true&#34;
              format_precedence &#34;crl&#34;

              border_enabled  &#34;false&#34;
              border_char     &#34;─&#34;
              border_format   &#34;#[fg=#6C7086]{char}&#34;
              border_position &#34;top&#34;

              mode_normal        &#34;#[bg=#${colors.base0B},fg=#${colors.base02},bold] NORMAL#[bg=#${colors.base03},fg=#${colors.base0B}]█&#34;
              mode_locked        &#34;#[bg=#${colors.base04},fg=#${colors.base02},bold] LOCKED #[bg=#${colors.base03},fg=#${colors.base04}]█&#34;
              mode_resize        &#34;#[bg=#${colors.base08},fg=#${colors.base02},bold] RESIZE#[bg=#${colors.base03},fg=#${colors.base08}]█&#34;
              mode_pane          &#34;#[bg=#${colors.base0D},fg=#${colors.base02},bold] PANE#[bg=#${colors.base03},fg=#${colors.base0D}]█&#34;
              mode_tab           &#34;#[bg=#${colors.base07},fg=#${colors.base02},bold] TAB#[bg=#${colors.base03},fg=#${colors.base07}]█&#34;
              mode_scroll        &#34;#[bg=#${colors.base0A},fg=#${colors.base02},bold] SCROLL#[bg=#${colors.base03},fg=#${colors.base0A}]█&#34;
              mode_enter_search  &#34;#[bg=#${colors.base0D},fg=#${colors.base02},bold] ENT-SEARCH#[bg=#${colors.base03},fg=#${colors.base0D}]█&#34;
              mode_search        &#34;#[bg=#${colors.base0D},fg=#${colors.base02},bold] SEARCHARCH#[bg=#${colors.base03},fg=#${colors.base0D}]█&#34;
              mode_rename_tab    &#34;#[bg=#${colors.base07},fg=#${colors.base02},bold] RENAME-TAB#[bg=#${colors.base03},fg=#${colors.base07}]█&#34;
              mode_rename_pane   &#34;#[bg=#${colors.base0D},fg=#${colors.base02},bold] RENAME-PANE#[bg=#${colors.base03},fg=#${colors.base0D}]█&#34;
              mode_session       &#34;#[bg=#${colors.base0E},fg=#${colors.base02},bold] SESSION#[bg=#${colors.base03},fg=#${colors.base0E}]█&#34;
              mode_move          &#34;#[bg=#${colors.base0F},fg=#${colors.base02},bold] MOVE#[bg=#${colors.base03},fg=#${colors.base0F}]█&#34;
              mode_prompt        &#34;#[bg=#${colors.base0D},fg=#${colors.base02},bold] PROMPT#[bg=#${colors.base03},fg=#${colors.base0D}]█&#34;
              mode_tmux          &#34;#[bg=#${colors.base09},fg=#${colors.base02},bold] TMUX#[bg=#${colors.base03},fg=#${colors.base09}]█&#34;

              // formatting for inactive tabs
              tab_normal              &#34;#[bg=#${colors.base03},fg=#${colors.base0D}]█#[bg=#${colors.base0D},fg=#${colors.base02},bold]{index} #[bg=#${colors.base02},fg=#${colors.base05},bold] {name}{floating_indicator}#[bg=#${colors.base03},fg=#${colors.base02},bold]█&#34;
              tab_normal_fullscreen   &#34;#[bg=#${colors.base03},fg=#${colors.base0D}]█#[bg=#${colors.base0D},fg=#${colors.base02},bold]{index} #[bg=#${colors.base02},fg=#${colors.base05},bold] {name}{fullscreen_indicator}#[bg=#${colors.base03},fg=#${colors.base02},bold]█&#34;
              tab_normal_sync         &#34;#[bg=#${colors.base03},fg=#${colors.base0D}]█#[bg=#${colors.base0D},fg=#${colors.base02},bold]{index} #[bg=#${colors.base02},fg=#${colors.base05},bold] {name}{sync_indicator}#[bg=#${colors.base03},fg=#${colors.base02},bold]█&#34;

              // formatting for the current active tab
              tab_active              &#34;#[bg=#${colors.base03},fg=#${colors.base li09}]█#[bg=#${colors.base09},fg=#${colors.base02},bold]{index} #[bg=#${colors.base02},fg=#${colors.base05},bold] {name}{floating_indicator}#[bg=#${colors.base03},fg=#${colors.base02},bold]█&#34;
              tab_active_fullscreen   &#34;#[bg=#${colors.base03},fg=#${colors.base09}]█#[bg=#${colors.base09},fg=#${colors.base02},bold]{index} #[bg=#${colors.base02},fg=#${colors.base05},bold] {name}{fullscreen_indicator}#[bg=#${colors.base03},fg=#${colors.base02},bold]█&#34;
              tab_active_sync         &#34;#[bg=#${colors.base03},fg=#${colors.base09}]█#[bg=#${colors.base09},fg=#${colors.base02},bold]{index} #[bg=#${colors.base02},fg=#${colors.base05},bold] {name}{sync_indicator}#[bg=#${colors.base03},fg=#${colors.base02},bold]█&#34;

              // separator between the tabs
              tab_separator           &#34;#[bg=#${colors.base00}] &#34;

              // indicators
              tab_sync_indicator       &#34; &#34;
              tab_fullscreen_indicator &#34; 󰊓&#34;
              tab_floating_indicator   &#34; 󰹙&#34;

              command_git_branch_command     &#34;git rev-parse --abbrev-ref HEAD&#34;
              command_git_branch_format      &#34;#[fg=blue] {stdout} &#34;
              command_git_branch_interval    &#34;10&#34;
              command_git_branch_rendermode  &#34;static&#34;

              datetime        &#34;#[fg=#6C7086,bold] {format} &#34;
              datetime_format &#34;%A, %d %b %Y %H:%M&#34;
              datetime_timezone &#34;Europe/London&#34;
          }
      }
      children
  }
}
</code></pre><h2 id="workflow">Workflow</h2>
<p>On start up of my PC, I open a terminal and run the <code>sesh</code> script pick a project, say banter bus.
Which should in theory resurrect the session if it was alive but seems to have issues with on Nix. I usually get
around this by pressing <code>ESC</code> but ideally, it would resurrect the session. Usually, though, It&rsquo;s not super complicated.
To set up most of my projects again.</p>
<p>I also usually hibernate my PC so I can carry on where I left off from. When working, I will often use the floating
pane which I toggle using <code>alt + p</code> and <code>w</code>. Where I will run commands like <code>task lint</code> to lint my code.
Or say <code>task dev</code> to start my app and view logs.</p>
<p>That&rsquo;s it! My Zellij setup and roughly how I use it.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Speaking Tips I Have Learnt From Conferences</title>
      <link>https://haseebmajid.dev/posts/2024-12-15-speaking-tips-i-ve-learnt/</link>
      <pubDate>Sun, 15 Dec 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-12-15-speaking-tips-i-ve-learnt/</guid>
      <description>&lt;p&gt;Since 2022 I have tried to speak at least one conference/meetup a year. The main reason for this was it forces to
really learn the area I want to present about a lot deeper. If you can explain something new to someone clearly
then it really shows you understand it very well.&lt;/p&gt;
&lt;p&gt;Recently, I was lucky enough to speak at &lt;a href=&#34;https://hachyderm.io/@golab@mastodon.uno/113475429722773280&#34;&gt;GoLabs 2024 in Florence&lt;/a&gt;,
which was a load of fun and a week later at the &lt;a href=&#34;https://www.meetup.com/londongophers/events/303598426/&#34;&gt;London Gophers November Meet&lt;/a&gt;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Since 2022 I have tried to speak at least one conference/meetup a year. The main reason for this was it forces to
really learn the area I want to present about a lot deeper. If you can explain something new to someone clearly
then it really shows you understand it very well.</p>
<p>Recently, I was lucky enough to speak at <a href="https://hachyderm.io/@golab@mastodon.uno/113475429722773280">GoLabs 2024 in Florence</a>,
which was a load of fun and a week later at the <a href="https://www.meetup.com/londongophers/events/303598426/">London Gophers November Meet</a>.</p>
<p>This was funnily the 3rd time I had presented a very similar talk (I did Conf42 earlier this year). The topic was
how we can create development environments for Golang using Nix.</p>
<p>I think generally speaking some of the best parts of meetups and conferences is not even the talks themselves it&rsquo;s the
networking. Meeting people who work at random companies you never would&rsquo;ve met otherwise etc. So I really do recommend
even if you don&rsquo;t intend to speak, try to go to a conference or two. Especially if your company has a learning budget
you can leverage.</p>
<h2 id="tips">Tips</h2>
<p>On to some tips that I&rsquo;ve learnt, or other people taught me that helped make me a better speaker. In no particular order.
Remember these are all my own opinion and from my own experiences as a presenter.</p>
<h3 id="topic">Topic</h3>
<p>The hardest part when I want to do a talk is actually coming up with the topic I would like to talk about. I generally
would like to discuss a topic that I think others don&rsquo;t much about and would find interesting, i.e.</p>
<ul>
<li>How to use docker with Python</li>
<li>Pocketbase</li>
<li>Nix dev envs with Go</li>
</ul>
<p>Coming up with topic that I feel passionate about is super important, as I think shows when you speak that you
really care about topic x. But it also means I have more motivation when creating the slides.</p>
<h3 id="slides">Slides</h3>
<p>We have a tendency to put everything you want people know on the slides. This can lead to them being crowded and
harder to read, too dense full of information.</p>
<p>One of the best bits of advice I was given, if someone can see your slides and fully understand your talk. What
does the value add of you as a speaker? Very little. So, in my opinion, the slides on their own shouldn&rsquo;t make sense.
You are there as a speaker to then present those slides such that it makes sense.</p>
<p>You want to add things like diagrams to drive your point home. Likewise, you may want to put key bullet points of topics you
would like to talk about.</p>
<p>Leave the details in the speaker notes that the audience cannot see. As not to crowd the slides.
In terms of software I really like <a href="https://revealjs.com/">reveal.js</a>. Which allow us to super fancy slides
(as HTML page), so super portable easy to share with others. I even host them on my Hugo (this blog).
Especially if you need to show code, have a look, and it is markdown-driven.</p>
<h3 id="practice">Practice</h3>
<p>Practice, practice and more practice. More likely than not you will be nervous just before you present. To make
sure you are well-prepared, to practice your presentation. Until you can almost do the talk from memory. This may
sound like a lot of effort, but it will really help you make your talk seem a lot smoother on the day.</p>
<p>Trust me, it can be a real pain, when you have a 40-min presentation needing to practice that multiple times a week
It can be actually annoying. Talking from experience here!!! But I think it just makes your talk so much better.</p>
<p>As I say on the day you will be nervous and even if you make a mistake it will be easier to recover as you have
Practised that talk so much. Sometimes I would just do 10 mins at a time, to make it more digestible, but as the date
gets closer I would practice the entire talk more often and try to time myself. As I have found I am ALWAYS slower
on the day and tend to need to speed up certain parts of my talk on the day itself.</p>
<h4 id="feedback">Feedback</h4>
<p>Alongside practice, I think feedback is so important both of my conference talks got so much better because people
gave me feedback. Especially my Euro Python talk about Docker, it was a lot worse than the final version I gave.</p>
<p>Either you can do this by presenting live to people, say over a video call with colleagues at work, or you can record
yourself send it to everyone you need to. Then they can give you feedback, the more detailed the better you don&rsquo;t
have to agree with everything and can decide what you want to change.</p>
<p>Feedback is key to improving yourself and your talk!</p>
<h3 id="talk-you-would-watch">Talk you would watch</h3>
<p>Finally, I think what helps me is to try to design a talk I would watch myself. At the end of the day, you are the one
spending time making and giving a talk. Rather than trying to work out what other people want, set out to make a talk you
would watch/enjoy.</p>
<p>I like talks that are a bit like stories, they build up the topic over the talk. Sometimes it&rsquo;s like a bit of
role play, we solved problem x but now what about problem y? Of course, this doesn&rsquo;t mean entirely ignoring feedback.
But don&rsquo;t feel the need to change your entire to talk to suit what someone else would like (generally speaking).</p>
<h3 id="rejection">Rejection</h3>
<p>Not a tip for speaking per se, but typically, you will submit your talk when a conference has a call for proposal.
I have seen most conference use either callforpapers or sessionize, which makes it easier to submit the proposal as
you can re-use your existing one.</p>
<p>But as is normal, I found you will face more rejections than conferences who accept your talk. However, that is fine
it&rsquo;s not to say your talk is bad. Sometimes people misunderstand what you wanted to present based on your abstract.
Sometimes it doesn&rsquo;t fit with the conference. Keep submitting, eventually you will get accepted, some conferences even
sometimes prefer to give first time speakers a go.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Wrap NixGL Around Package in Home Manager</title>
      <link>https://haseebmajid.dev/posts/2024-10-15-how-to-wrap-nixgl-around-package-in-home-manager/</link>
      <pubDate>Tue, 15 Oct 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-10-15-how-to-wrap-nixgl-around-package-in-home-manager/</guid>
      <description>&lt;p&gt;I use Nix mainly with home manager on my Ubuntu laptop, and for the most part, it works fine. However, some apps installed
using Nix, need to use &lt;a href=&#34;https://github.com/nix-community/nixGL&#34;&gt;nixGL&lt;/a&gt;. A wrapper tool for OpenGL, allowing Nix installed
tooling to use the system&amp;rsquo;s OpenGL and Vulkan APIs. Some apps including kitty and Firefox (mainly for Google Meet).&lt;/p&gt;
&lt;p&gt;There is currently a &lt;a href=&#34;https://github.com/nix-community/home-manager/pull/5355&#34;&gt;branch&lt;/a&gt; in home-manager we can pull
into our config, which provides a convenient way to wrap these apps in nixGL rather than needing to specify say
&lt;code&gt;nixGLIntel kitty&lt;/code&gt; in our terminal or say Hyprland config (key bindings).&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I use Nix mainly with home manager on my Ubuntu laptop, and for the most part, it works fine. However, some apps installed
using Nix, need to use <a href="https://github.com/nix-community/nixGL">nixGL</a>. A wrapper tool for OpenGL, allowing Nix installed
tooling to use the system&rsquo;s OpenGL and Vulkan APIs. Some apps including kitty and Firefox (mainly for Google Meet).</p>
<p>There is currently a <a href="https://github.com/nix-community/home-manager/pull/5355">branch</a> in home-manager we can pull
into our config, which provides a convenient way to wrap these apps in nixGL rather than needing to specify say
<code>nixGLIntel kitty</code> in our terminal or say Hyprland config (key bindings).</p>
<p>First, let&rsquo;s import the relevant code from the branch/PR:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"> <span class="n">imports</span> <span class="err">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># TODO: remove when https://github.com/nix-community/home-manager/pull/5355 gets merged:</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="nb">builtins</span><span class="o">.</span><span class="n">fetchurl</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;https://raw.githubusercontent.com/Smona/home-manager/nixgl-compat/modules/misc/nixgl.nix&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">sha256</span> <span class="o">=</span> <span class="s2">&#34;01dkfr9wq3ib5hlyq9zq662mp0jl42fw3f6gd2qgdf8l8ia78j7i&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span></code></pre></div><p>Then, simply, we can do something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="n">programs</span> <span class="err">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">kitty</span><span class="o">.</span><span class="n">package</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">lib</span><span class="o">.</span><span class="n">nixGL</span><span class="o">.</span><span class="n">wrap</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">kitty</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">firefox</span><span class="o">.</span><span class="n">package</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">lib</span><span class="o">.</span><span class="n">nixGL</span><span class="o">.</span><span class="n">wrap</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">firefox</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Then we should be able to use the apps like normal, such as using our app launcher like rofi.
That&rsquo;s It!</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/nixicle/-/commit/512df880d52908a232ad48f7a043c4cbdd0264bc">Commit with nixGL wrap in my own config</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Set up Cloudflare Tunnels With Traefik (In NixOS)</title>
      <link>https://haseebmajid.dev/posts/2024-09-30-how-to-setup-cloudflare-tunnels-to-point-to-service-behind-traefik/</link>
      <pubDate>Mon, 30 Sep 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-09-30-how-to-setup-cloudflare-tunnels-to-point-to-service-behind-traefik/</guid>
      <description>&lt;p&gt;Recently, I was setting up Navidrome, kind of like a self-hosted Spotify, in my home lab and I wanted to set it up to use
proxy auth in Authentik. But to do this I needed to use with a reverse proxy, i.e. Traefik. In this article
I will show you how to you can point your Cloudflare tunnel to Traefik and have that forward the request to the service.&lt;/p&gt;
&lt;h2 id=&#34;prerequisite&#34;&gt;Prerequisite&lt;/h2&gt;
&lt;p&gt;In this article, I assume you are already familiar with Cloudflare Tunnels and Traefik.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Recently, I was setting up Navidrome, kind of like a self-hosted Spotify, in my home lab and I wanted to set it up to use
proxy auth in Authentik. But to do this I needed to use with a reverse proxy, i.e. Traefik. In this article
I will show you how to you can point your Cloudflare tunnel to Traefik and have that forward the request to the service.</p>
<h2 id="prerequisite">Prerequisite</h2>
<p>In this article, I assume you are already familiar with Cloudflare Tunnels and Traefik.</p>
<h2 id="background">Background</h2>
<p>Cloudflare tunnels are an easy way to allow access to apps running on a server, i.e. home lab. Where you only want
expose, say, a few services. When a client connects, they connect to Cloudflare and then Cloudflare makes a secure
connection to your server. Which is running the <code>cloudflared</code> daemon.</p>
<p>Where my Cloudflare nix config looks something like this <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">config</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">lib</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}:</span>
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="n">lib</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="n">lib</span><span class="o">.</span><span class="n">nixicle</span><span class="p">;</span> <span class="k">let</span>
</span></span><span class="line"><span class="cl">  <span class="n">cfg</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">services</span><span class="o">.</span><span class="n">nixicle</span><span class="o">.</span><span class="n">cloudflared</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">in</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">options</span><span class="o">.</span><span class="n">services</span><span class="o">.</span><span class="n">nixicle</span><span class="o">.</span><span class="n">cloudflared</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="n">mkEnableOption</span> <span class="s2">&#34;Enable The cloudflared (tunnel) service&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">config</span> <span class="o">=</span> <span class="n">mkIf</span> <span class="n">cfg</span><span class="o">.</span><span class="n">enable</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">sops</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">cloudflared</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">sopsFile</span> <span class="o">=</span> <span class="sr">../secrets.yaml</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">owner</span> <span class="o">=</span> <span class="s2">&#34;cloudflared&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">services</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">cloudflared</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">tunnels</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="s2">&#34;ec0b6af0-a823-4616-a08b-b871fd2c7f58&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">credentialsFile</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">sops</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">cloudflared</span><span class="o">.</span><span class="n">path</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">default</span> <span class="o">=</span> <span class="s2">&#34;http_status:404&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">          <span class="p">};</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The other apps I have set up in Cloudflare tunnels point directly to the service itself. This is the first one
I needed to point to Traefik, and then Traefik would forward the request to Navidrome.</p>
<p>Where my Traefik setup looks something <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">config</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">lib</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}:</span>
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="n">lib</span><span class="p">;</span> <span class="k">let</span>
</span></span><span class="line"><span class="cl">  <span class="n">cfg</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">services</span><span class="o">.</span><span class="n">nixicle</span><span class="o">.</span><span class="n">traefik</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">in</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">options</span><span class="o">.</span><span class="n">services</span><span class="o">.</span><span class="n">nixicle</span><span class="o">.</span><span class="n">traefik</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="n">mkEnableOption</span> <span class="s2">&#34;Enable traefik&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">config</span> <span class="o">=</span> <span class="n">mkIf</span> <span class="n">cfg</span><span class="o">.</span><span class="n">enable</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">services</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">traefik</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">staticConfigOptions</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="c1"># removed config to simplify</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">          <span class="n">entryPoints</span><span class="o">.</span><span class="n">web</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">address</span> <span class="o">=</span> <span class="s2">&#34;0.0.0.0:80&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">http</span><span class="o">.</span><span class="n">redirections</span><span class="o">.</span><span class="n">entryPoint</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">              <span class="n">to</span> <span class="o">=</span> <span class="s2">&#34;websecure&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">              <span class="n">scheme</span> <span class="o">=</span> <span class="s2">&#34;https&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">              <span class="n">permanent</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">};</span>
</span></span><span class="line"><span class="cl">          <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">          <span class="n">entryPoints</span><span class="o">.</span><span class="n">websecure</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">address</span> <span class="o">=</span> <span class="s2">&#34;0.0.0.0:443&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">http</span><span class="o">.</span><span class="n">tls</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">              <span class="n">certResolver</span> <span class="o">=</span> <span class="s2">&#34;letsencrypt&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">              <span class="n">domains</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span>
</span></span><span class="line"><span class="cl">                  <span class="n">main</span> <span class="o">=</span> <span class="s2">&#34;homelab.haseebmajid.dev&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                  <span class="n">sans</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;*.homelab.haseebmajid.dev&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span>
</span></span><span class="line"><span class="cl">                  <span class="n">main</span> <span class="o">=</span> <span class="s2">&#34;haseebmajid.dev&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                  <span class="n">sans</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;*.haseebmajid.dev&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">              <span class="p">];</span>
</span></span><span class="line"><span class="cl">            <span class="p">};</span>
</span></span><span class="line"><span class="cl">          <span class="p">};</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Things to note about my setup (now shown here) but Traefik is set up to be able to provision certificates from let&rsquo;s encrypt
using Cloudflare DNS challenge (see full config linked above).</p>
<h2 id="setup">Setup</h2>
<p></p>
<p>We can see here this is how I configured my tunnel, first I point to traefik on port 443, i.e. https. As Traefik
I have set up always expects traffic on 443. Also notice the <code>origin server name</code> is <code>navidrome.haseebmajid.dev</code>.
This will get passed to Traefik.</p>
<p>Or in my Nix config I have defined it like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="n">services</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">cloudflared</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">tunnels</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;ec0b6af0-a823-4616-a08b-b871fd2c7f58&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">ingress</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="s2">&#34;navidrome.haseebmajid.dev&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">service</span> <span class="o">=</span> <span class="s2">&#34;https://localhost&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">originRequest</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">              <span class="n">originServerName</span> <span class="o">=</span> <span class="s2">&#34;navidrome.haseebmajid.dev&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">};</span>
</span></span><span class="line"><span class="cl">          <span class="p">};</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Finally, the last bit to connect the two bits together, the Traefik config:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">traefik</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">dynamicConfigOptions</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">http</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">services</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">navidrome</span><span class="o">.</span><span class="n">loadBalancer</span><span class="o">.</span><span class="n">servers</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">              <span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;http://localhost:4533&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">          <span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">routers</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">navidrome</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">entryPoints</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;websecure&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">            <span class="n">rule</span> <span class="o">=</span> <span class="s2">&#34;Host(`navidrome.haseebmajid.dev`)&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">service</span> <span class="o">=</span> <span class="s2">&#34;navidrome&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">tls</span><span class="o">.</span><span class="n">certResolver</span> <span class="o">=</span> <span class="s2">&#34;letsencrypt&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">middlewares</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;authentik&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">          <span class="p">};</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Pointing to our local service <code>http://localhost:4533</code> where Navidrome is running.
We also tell it that if we get a request for host <code>navidrome.haseebmajid.dev</code> it should forward it to that
localhost service. We also tell it to provision a certificate using letsencrypt. Though you may not need this
as Cloudflare does it for you.</p>
<p>However, Cloudflare will only do it on direct subdomains, i.e. it won&rsquo;t work on <code>navidrome.homelab.haseebmajid.dev</code>.
Because it&rsquo;s a subdomain of a subdomain.</p>
<p>Then tell it to use the <code>authentik</code> middleware, which you can find out how to set up
<a href="/posts/2024-09-06-how-to-setup-authentik-forward-auth-with-traefik-on-nixos/">here</a>. This tells Traefik to forward
the request to Authentik, so we can correctly authenticate the user.</p>
<p>That&rsquo;s it, we should be able to access our service Navidrome when it&rsquo;s all deployed. The request is going from
Client → Cloudflare →Traefik → Navidrome.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://gitlab.com/hmajid2301/nixicle/-/blob/39654a157bc95d02ad24b4fd9bb2379d3173f53b/modules/nixos/services/cloudflared/default.nix">https://gitlab.com/hmajid2301/nixicle/-/blob/39654a157bc95d02ad24b4fd9bb2379d3173f53b/modules/nixos/services/cloudflared/default.nix</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://gitlab.com/hmajid2301/nixicle/-/blob/39654a157bc95d02ad24b4fd9bb2379d3173f53b/modules/nixos/services/traefik/default.nix">https://gitlab.com/hmajid2301/nixicle/-/blob/39654a157bc95d02ad24b4fd9bb2379d3173f53b/modules/nixos/services/traefik/default.nix</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Cancel Stale Pipelines in GitLab CI</title>
      <link>https://haseebmajid.dev/posts/2024-09-15-til-how-to-cancel-stale-pipelines-in-gitlab-ci/</link>
      <pubDate>Sun, 15 Sep 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-09-15-til-how-to-cancel-stale-pipelines-in-gitlab-ci/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Cancel Stale Pipelines in GitLab CI&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Today I learnt that we can cancel old pipelines in MRs. For example, you push a new commit, and you don&amp;rsquo;t care about the
old pipeline running any more. You want to cancel them to save CI minutes etc.&lt;/p&gt;
&lt;p&gt;Here is an example, you can see the 2nd pipeline is cancelled:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;We need to add this to our &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; file. Where the &lt;code&gt;default.interruptible&lt;/code&gt; marks every job as it can be
cancelled &lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Cancel Stale Pipelines in GitLab CI</strong></p>
<p>Today I learnt that we can cancel old pipelines in MRs. For example, you push a new commit, and you don&rsquo;t care about the
old pipeline running any more. You want to cancel them to save CI minutes etc.</p>
<p>Here is an example, you can see the 2nd pipeline is cancelled:</p>
<p></p>
<p>We need to add this to our <code>.gitlab-ci.yml</code> file. Where the <code>default.interruptible</code> marks every job as it can be
cancelled <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">workflow</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">auto_cancel</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">on_new_commit</span><span class="p">:</span><span class="w"> </span><span class="l">interruptible</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">default</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">interruptible</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span></code></pre></div><p>Then on new commits, we cancel all interruptible jobs. Just make sure to be careful where jobs actually deploy stuff.
You don&rsquo;t want to partially deploy things. But for jobs like running tests, linting etc this should be all good.</p>
<ul>
<li><a href="https://gitlab.com/hmajid2301/banterbus/-/blob/1ef5ae17e81b75576d7df02a60c9e95e11fc6d96/.gitlab-ci.yml">Example gitlab pipeline</a></li>
</ul>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://docs.gitlab.com/ee/ci/yaml/index.html#interruptible##">https://docs.gitlab.com/ee/ci/yaml/index.html#interruptible##</a> Appendix&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Get Shell Completions in Nix Shell With Direnv</title>
      <link>https://haseebmajid.dev/posts/2024-09-12-til-how-to-get-shell-completions-in-nix-shell-with-direnv/</link>
      <pubDate>Thu, 12 Sep 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-09-12-til-how-to-get-shell-completions-in-nix-shell-with-direnv/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Get Shell Completions in Nix Shell With Direnv&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When shell completions don&amp;rsquo;t work with direnv, you may need to use &lt;code&gt;nix develop&lt;/code&gt; to load the shell manually.&lt;/p&gt;
&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;p&gt;I am using &lt;code&gt;nix-direnv&lt;/code&gt;, with nix devshell to autoload into my development environments. I changed the directory
and the devshell is automagically loaded without me doing anything, which is great. Provides me with a bunch of tools
specific for that project. With all the benefits of Nix, mainly reproducibility.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Get Shell Completions in Nix Shell With Direnv</strong></p>
<p>When shell completions don&rsquo;t work with direnv, you may need to use <code>nix develop</code> to load the shell manually.</p>
<h2 id="background">Background</h2>
<p>I am using <code>nix-direnv</code>, with nix devshell to autoload into my development environments. I changed the directory
and the devshell is automagically loaded without me doing anything, which is great. Provides me with a bunch of tools
specific for that project. With all the benefits of Nix, mainly reproducibility.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ z banterbus/
</span></span><span class="line"><span class="cl">direnv: loading ~/projects/banterbus/.envrc
</span></span><span class="line"><span class="cl">direnv: using flake
</span></span><span class="line"><span class="cl">direnv: nix-direnv: Using cached dev shell
</span></span><span class="line"><span class="cl">direnv: <span class="nb">export</span> +AR +AS +CC +CONFIG_SHELL +CXX +GOTOOLDIR +HOST_PATH +IN_NIX_SHELL +LD +NIX_BINTOOLS +NIX_BINTOOLS_WRAPPER_TARGET_HOST_x86_64_unknown_linux_gnu +NIX_BUILD_CORES +NIX_CC +NIX_CC_WRAPPER_TARGET_HOST_x86_64_unknown_linux_gnu +NIX_CFLAGS_COMPILE +NIX_ENFORCE_NO_NATIVE +NIX_HARDENING_ENABLE +NIX_LDFLAGS +NIX_STORE +NM +OBJCOPY +OBJDUMP +PLAYWRIGHT_BROWSERS_PATH +PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD +RANLIB +READELF +SIZE +SOURCE_DATE_EPOCH +STRINGS +STRIP +__structuredAttrs +buildInputs +buildPhase +builder +cmakeFlags +configureFlags +depsBuildBuild +depsBuildBuildPropagated +depsBuildTarget +depsBuildTargetPropagated +depsHostHost +depsHostHostPropagated +depsTargetTarget +depsTargetTargetPropagated +doCheck +doInstallCheck +dontAddDisableDepTrack +hardeningDisable +mesonFlags +name +nativeBuildInputs +out +outputs +patches +phases +preferLocalBuild +propagatedBuildInputs +propagatedNativeBuildInputs +shell +shellHook +stdenv +strictDeps +system ~PATH ~XDG_DATA_DIRS
</span></span></code></pre></div><p>Where my mkShell function looks something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">pkgs</span><span class="o">.</span><span class="n">mkShell</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">hardeningDisable</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;all&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="n">shellHook</span> <span class="o">=</span> <span class="s1">&#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">      export PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
</span></span></span><span class="line"><span class="cl"><span class="s1">      export PLAYWRIGHT_BROWSERS_PATH=&#34;</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">playwright-driver</span><span class="o">.</span><span class="n">browsers</span><span class="si">}</span><span class="s1">&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">      </span><span class="si">${</span><span class="n">pre-commit-check</span><span class="o">.</span><span class="n">shellHook</span><span class="si">}</span><span class="s1">
</span></span></span><span class="line"><span class="cl"><span class="s1">    &#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">buildInputs</span> <span class="o">=</span> <span class="n">pre-commit-check</span><span class="o">.</span><span class="n">enabledPackages</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">packages</span> <span class="o">=</span> <span class="k">with</span> <span class="n">pkgs</span><span class="p">;</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="c1"># TODO: workout how to use go env</span>
</span></span><span class="line"><span class="cl">      <span class="c1"># goEnv</span>
</span></span><span class="line"><span class="cl">      <span class="n">gomod2nix</span>
</span></span><span class="line"><span class="cl">      <span class="n">go_1_22</span>
</span></span><span class="line"><span class="cl">      <span class="n">playwright-test</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">goose</span>
</span></span><span class="line"><span class="cl">      <span class="n">air</span>
</span></span><span class="line"><span class="cl">      <span class="n">golangci-lint</span>
</span></span><span class="line"><span class="cl">      <span class="n">gotools</span>
</span></span><span class="line"><span class="cl">      <span class="n">gotestsum</span>
</span></span><span class="line"><span class="cl">      <span class="n">gocover-cobertura</span>
</span></span><span class="line"><span class="cl">      <span class="n">go-task</span>
</span></span><span class="line"><span class="cl">      <span class="n">go-mockery</span>
</span></span><span class="line"><span class="cl">      <span class="n">goreleaser</span>
</span></span><span class="line"><span class="cl">      <span class="n">golines</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">tailwindcss</span>
</span></span><span class="line"><span class="cl">      <span class="n">templ</span>
</span></span><span class="line"><span class="cl">      <span class="n">sqlc</span>
</span></span><span class="line"><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>You can see I have certain tools I want other developers to have access to like <code>go-task</code>, which they will automatically
get when they load into the devshell.</p>
<h2 id="issue">Issue</h2>
<p>This is all great; however, I have noticed that shell completions stopped working. Where I can normally do <code>task &lt;TAB&gt;</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ task tests:e2e
</span></span><span class="line"><span class="cl">build:dev  <span class="o">(</span>Build the app <span class="k">for</span> development, generates all the files needed <span class="k">for</span> the binary.<span class="o">)</span>  generate:sqlc  <span class="o">(</span>Generates the code to interact with SQL DB.<span class="o">)</span>
</span></span><span class="line"><span class="cl">coverage                            <span class="o">(</span>Run the integration tests and gets the code coverage<span class="o">)</span>  lint                                      <span class="o">(</span>Runs the linter.<span class="o">)</span>
</span></span><span class="line"><span class="cl">dev                                       <span class="o">(</span>Start the app in dev mode with live-reloading.<span class="o">)</span>  release                              <span class="o">(</span>Release the CLI tool.<span class="o">)</span>
</span></span><span class="line"><span class="cl">docker:build                                            <span class="o">(</span>Builds a Docker image using Nix.<span class="o">)</span>  tests                                  <span class="o">(</span>Runs all the tests.<span class="o">)</span>
</span></span><span class="line"><span class="cl">docker:load                                <span class="o">(</span>Loads the Docker image from tar <span class="o">(</span>in results<span class="o">)</span>.<span class="o">)</span>  tests:e2e                  <span class="o">(</span>Runs e2e tests with playwright.<span class="o">)</span>
</span></span><span class="line"><span class="cl">docker:publish                                                <span class="o">(</span>Publishes the Docker image<span class="o">)</span>  tests:integration          <span class="o">(</span>Runs all the integration tests.<span class="o">)</span>
</span></span><span class="line"><span class="cl">format                                                               <span class="o">(</span>Runs the formatter.<span class="o">)</span>  tests:unit                        <span class="o">(</span>Runs all the unit tests.<span class="o">)</span>
</span></span></code></pre></div><p>However, in the devshell this does not work until I load into the shell manually <code>nix develop</code>. Which does defeat
the purpose of using <code>nix-direnv</code> as you need to manually run a command now.</p>
<p>I don&rsquo;t know what causes this and will do a deeper dive at some point 🤔.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Set up Authentik Forward Auth With Traefik on NixOS</title>
      <link>https://haseebmajid.dev/posts/2024-09-06-how-to-setup-authentik-forward-auth-with-traefik-on-nixos/</link>
      <pubDate>Fri, 06 Sep 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-09-06-how-to-setup-authentik-forward-auth-with-traefik-on-nixos/</guid>
      <description>&lt;p&gt;In this post, I will show you how you can set up forward auth for a single host in Authentik, with Traefik as our reverse
proxy on NixOS. This is particularly useful way to protect apps that don&amp;rsquo;t have any built in auth.&lt;/p&gt;
&lt;p&gt;Authentik is a great app which can handle authentication for almost all of our home lab. So we only need to log in
with Authentik, to log in to any of our apps. Rather than needing separate passwords and usernames for each app.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this post, I will show you how you can set up forward auth for a single host in Authentik, with Traefik as our reverse
proxy on NixOS. This is particularly useful way to protect apps that don&rsquo;t have any built in auth.</p>
<p>Authentik is a great app which can handle authentication for almost all of our home lab. So we only need to log in
with Authentik, to log in to any of our apps. Rather than needing separate passwords and usernames for each app.</p>
<p>You may see an error like this, when trying to access the app:</p>
<p><img
        loading="lazy"
        src="/posts/2024-09-06-how-to-setup-authentik-forward-auth-with-traefik-on-nixos/images/404.png"
        type=""
        alt="404"
        
      /></p>
<p>or you may see this</p>
<p><img
        loading="lazy"
        src="/posts/2024-09-06-how-to-setup-authentik-forward-auth-with-traefik-on-nixos/images/not-found.png"
        type=""
        alt="404"
        
      /></p>
<h2 id="background">Background</h2>
<p>As per is the case with everything I do, rather than sticking to my home lab in K3S. I decided to move some of it to
NixOS. The main reason for this being, it felt like there was a lot of boilerplate, sometimes 4 files just to deploy
an app. I felt like I was getting more frustrated and not really spending any time setting up my home lab.</p>
<p>So hence I decided to have a look at how I could do it with NixOS itself. I would lose out on some of the HA features, i.e.
if a node goes down, pods would get moved to another node. But for now, it would be good enough.</p>
<p>One thing that took me a while to figure out was how to set up forward auth like I had working with my Kubernetes
cluster. Such as protecting apps from the &ldquo;arr&rdquo; stack, such as Sonarr.</p>
<p>As seems to be the norm for these blog posts it took me longer than I would care to admit, so let me show you how
I did it.</p>
<h2 id="getting-started">Getting started</h2>
<p>For this, I will use the embedded outpost we have with Authentik and you are already using Traefik as your reverse proxy.</p>
<h3 id="authentik">Authentik</h3>
<p>To set up Authentik, I used <a href="https://github.com/nix-community/authentik-nix">authentik-nix</a>. Where my config looks
something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">services</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">authentik</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">environmentFile</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">sops</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">authenik_env</span><span class="o">.</span><span class="n">path</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">settings</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">email</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="c1"># email settings</span>
</span></span><span class="line"><span class="cl">          <span class="p">};</span>
</span></span><span class="line"><span class="cl">          <span class="n">disable_startup_analytics</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">          <span class="n">avatars</span> <span class="o">=</span> <span class="s2">&#34;initials&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Where my authentik_env config in sops looks like:</p>
<pre tabindex="0"><code>authenik_env: |
    AUTHENTIK_SECRET_KEY=
    AUTHENTIK_EMAIL__PASSWORD=
</code></pre><p>Then the key bit I was missing the relevant Traefik config:</p>
<ul>
<li>Define a new middleware</li>
<li>Define a new service</li>
<li>Define a new route to take us to the Authentik embedded outpost</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="n">services</span><span class="o">.</span><span class="n">traefik</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">dynamicConfigOptions</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">http</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">middlewares</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">authentik</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">forwardAuth</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">              <span class="n">tls</span><span class="o">.</span><span class="n">insecureSkipVerify</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">              <span class="n">address</span> <span class="o">=</span> <span class="s2">&#34;https://localhost:9443/outpost.goauthentik.io/auth/traefik&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">              <span class="n">trustForwardHeader</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">              <span class="n">authResponseHeaders</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;X-authentik-username&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;X-authentik-groups&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;X-authentik-email&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;X-authentik-name&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;X-authentik-uid&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;X-authentik-jwt&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;X-authentik-meta-jwks&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;X-authentik-meta-outpost&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;X-authentik-meta-provider&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;X-authentik-meta-app&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;X-authentik-meta-version&#34;</span>
</span></span><span class="line"><span class="cl">              <span class="p">];</span>
</span></span><span class="line"><span class="cl">            <span class="p">};</span>
</span></span><span class="line"><span class="cl">          <span class="p">};</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">services</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">auth</span><span class="o">.</span><span class="n">loadBalancer</span><span class="o">.</span><span class="n">servers</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">              <span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;http://localhost:9000&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">          <span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">routers</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">auth</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">entryPoints</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;websecure&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">            <span class="n">rule</span> <span class="o">=</span> <span class="s2">&#34;Host(`authentik.haseebmajid.dev`) || HostRegexp(`{subdomain:[a-z0-9]+}.haseebmajid.com`) &amp;&amp; PathPrefix(`/outpost.goauthentik.io/`)&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">service</span> <span class="o">=</span> <span class="s2">&#34;auth&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">tls</span><span class="o">.</span><span class="n">certResolver</span> <span class="o">=</span> <span class="s2">&#34;letsencrypt&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">          <span class="p">};</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl"> <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The main bit I was missing was the address I needed to use the port <code>9443</code> to redirect to the embed outpost.
Then creating the relevant auth redirect:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl">  <span class="n">auth</span> <span class="err">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">entryPoints</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;websecure&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="n">rule</span> <span class="o">=</span> <span class="s2">&#34;Host(`authentik.haseebmajid.dev`) || HostRegexp(`{subdomain:[a-z0-9]+}.haseebmajid.com`) &amp;&amp; PathPrefix(`/outpost.goauthentik.io/`)&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">service</span> <span class="o">=</span> <span class="s2">&#34;auth&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">tls</span><span class="o">.</span><span class="n">certResolver</span> <span class="o">=</span> <span class="s2">&#34;letsencrypt&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span></code></pre></div><p>Where you should adjust <code>authentik.haseebmajid.dev</code> to match the domain you are using to access authentik on.
Then you can simply follow the <a href="https://docs.goauthentik.io/integrations/services/sonarr/">docs</a> and should be
able to repeat this for all apps you want to set this forward auth for.</p>
<p>Finally, in our Sonarr Traefik config, we would like to add our middleware that we just defined above:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl">  <span class="n">sonarr</span> <span class="err">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">entryPoints</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;websecure&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="n">rule</span> <span class="o">=</span> <span class="s2">&#34;Host(`sonarr.bare.homelab.haseebmajid.dev`)&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">service</span> <span class="o">=</span> <span class="s2">&#34;sonarr&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">tls</span><span class="o">.</span><span class="n">certResolver</span> <span class="o">=</span> <span class="s2">&#34;letsencrypt&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">middlewares</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;authentik&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span></code></pre></div><p>That&rsquo;s about it! The main thing I was missing was that <code>https://localhost:9443/outpost.goauthentik.io/auth/traefik</code>
especially the port. After I added that everything worked like a dream.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li>My home lab Nix config
<ul>
<li><a href="https://gitlab.com/hmajid2301/nixicle/-/blob/58ba59fee49b2533f1e047acae7fc06d6192667d/modules/nixos/services/arr/default.nix#L143">Sonarr Traefik config</a></li>
<li><a href="https://gitlab.com/hmajid2301/nixicle/-/blob/58ba59fee49b2533f1e047acae7fc06d6192667d/modules/nixos/services/authentik/default.nix#L55">Authentik config</a></li>
</ul>
</li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Setup TLS Certificate With Traefik &amp; Tailscale on NixOS</title>
      <link>https://haseebmajid.dev/posts/2024-08-19-setup-tls-certificate-with-traefik-tailscale-on-nixos/</link>
      <pubDate>Mon, 19 Aug 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-08-19-setup-tls-certificate-with-traefik-tailscale-on-nixos/</guid>
      <description>&lt;p&gt;Recently I have been playing around with running a homelab directly on a NixOS machine without kubernetes.
I didn&amp;rsquo;t want to bother to have to setup certificates using Traefik (DNS challenge) and Cloudflare. I wanted to use
the certificate that comes with Tailscale (wireguard VPN I use to connect to my home lab).&lt;/p&gt;
&lt;p&gt;In this post I will show you how I set this up as a Nix module.&lt;/p&gt;
&lt;h2 id=&#34;nix&#34;&gt;Nix&lt;/h2&gt;
&lt;p&gt;Let us look at the relevant Nix code.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Recently I have been playing around with running a homelab directly on a NixOS machine without kubernetes.
I didn&rsquo;t want to bother to have to setup certificates using Traefik (DNS challenge) and Cloudflare. I wanted to use
the certificate that comes with Tailscale (wireguard VPN I use to connect to my home lab).</p>
<p>In this post I will show you how I set this up as a Nix module.</p>
<h2 id="nix">Nix</h2>
<p>Let us look at the relevant Nix code.</p>
<h3 id="home-assistant">Home Assistant</h3>
<p>In my example I wanted to expose a home assistant instance, so I could access my smart plugs to turn on/off my home lab
remotely. So my home-assistant is setup like so exposing itself on port 8123.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="n">services</span><span class="o">.</span><span class="n">home-assistant</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">openFirewall</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">extraComponents</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;esphome&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;met&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;radio_browser&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="n">extraPackages</span> <span class="o">=</span> <span class="n">python3Packages</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="n">python3Packages</span><span class="p">;</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="n">numpy</span>
</span></span><span class="line"><span class="cl">      <span class="n">aiodhcpwatcher</span>
</span></span><span class="line"><span class="cl">      <span class="n">aiodiscover</span>
</span></span><span class="line"><span class="cl">      <span class="n">gtts</span>
</span></span><span class="line"><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="n">config</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">http</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">server_port</span> <span class="o">=</span> <span class="mi">8123</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">use_x_forwarded_for</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">trusted_proxies</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;127.0.0.1&#34;</span> <span class="s2">&#34;::1&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="traefik">Traefik</h3>
<p>Let us enable the traefik service on our machine like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="n">services</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">tailscale</span><span class="o">.</span><span class="n">permitCertUid</span> <span class="o">=</span> <span class="s2">&#34;traefik&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">traefik</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">staticConfigOptions</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">certificatesResolvers</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">tailscale</span><span class="o">.</span><span class="n">tailscale</span> <span class="o">=</span> <span class="p">{};</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>We also want to allow traefik to fetch the TLS cert for tailscale (this is the name of the user).
Then add a new resolver called tailscale of type tailscale. We probably could&rsquo;ve name the first one like <code>vpn.tailscale</code>.
But alas, I prefer it this way.</p>
<p>Then in we also want to define the following traefik config we want to do the following:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="n">services</span><span class="o">.</span><span class="n">traefik</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="n">dynamicConfigOptions</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">http</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">services</span><span class="o">.</span><span class="n">homeAssistant</span><span class="o">.</span><span class="n">loadBalancer</span><span class="o">.</span><span class="n">servers</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;http://localhost:8123&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">routers</span><span class="o">.</span><span class="n">homeAssistant</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">entryPoints</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;websecure&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">      <span class="n">rule</span> <span class="o">=</span> <span class="s2">&#34;Host(`s100.INSERT_TAILNET_TNAME.ts.net`)&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">service</span> <span class="o">=</span> <span class="s2">&#34;homeAssistant&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">tls</span><span class="o">.</span><span class="n">certResolver</span> <span class="o">=</span> <span class="s2">&#34;tailscale&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>I usually do this with the service itself or on the config specific to the machine s100 in my case.
Notice we match the name <code>homeAssistant</code> in our routers and our dynamic http services.</p>
<p>That&rsquo;s it deploy your changes to the machine and then you should be able to access home assistant on <a href="http://s100.INSERT_TAILNET_TNAME.ts.net">http://s100.INSERT_TAILNET_TNAME.ts.net</a>.</p>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">One Service</summary>
  
  I am yet to figure out how to expose more than one service this way, i.e. using subdomains.
Potentially we could do something with the path i.e. <code>/home-assistant</code> but I have not figured it out yet.
</details>

<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/dotfiles/-/blob/85b52b8b8d3948abe9dd66942d393ba82ae83649/modules/nixos/services/traefik/default.nix">My traefik config</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Configure Kitty on Nix to Show Emojis</title>
      <link>https://haseebmajid.dev/posts/2024-08-06-how-to-configure-kitty-on-nix-to-show-emojis/</link>
      <pubDate>Tue, 06 Aug 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-08-06-how-to-configure-kitty-on-nix-to-show-emojis/</guid>
      <description>&lt;p&gt;Recently, I was trying to configure the terminal emulator Kitty with Nix and Stylix, and I was having issues with it
showing emojis. It took me a lot longer to figure out than I would like to admit. So I decided to write a blog post,
showing you how I did it and perhaps saving you some time.&lt;/p&gt;
&lt;p&gt;I did this in my &lt;a href=&#34;https://gitlab.com/hmajid2301/dotfiles/-/blob/79d1ddee586a98816af3f5605de3059d675d58fe/modules/home/cli/terminals/kitty/default.nix&#34;&gt;home-manger config&lt;/a&gt;.
First we install the &lt;code&gt;Symbols Nerd Font&lt;/code&gt;, there are two ways we can normally add nerd fonts. Either patch our font,
which I had already done with my font Mono Lisa or use this font (symbols), alongside your font (as a fallback).
I haven&amp;rsquo;t fully managed to get that to work on my system, so I have both normal Mono Lisa and Nerd Font variants on my
system at the moment. Else other apps besides Kitty break. Anyway, tangent aside, you can install like this:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Recently, I was trying to configure the terminal emulator Kitty with Nix and Stylix, and I was having issues with it
showing emojis. It took me a lot longer to figure out than I would like to admit. So I decided to write a blog post,
showing you how I did it and perhaps saving you some time.</p>
<p>I did this in my <a href="https://gitlab.com/hmajid2301/dotfiles/-/blob/79d1ddee586a98816af3f5605de3059d675d58fe/modules/home/cli/terminals/kitty/default.nix">home-manger config</a>.
First we install the <code>Symbols Nerd Font</code>, there are two ways we can normally add nerd fonts. Either patch our font,
which I had already done with my font Mono Lisa or use this font (symbols), alongside your font (as a fallback).
I haven&rsquo;t fully managed to get that to work on my system, so I have both normal Mono Lisa and Nerd Font variants on my
system at the moment. Else other apps besides Kitty break. Anyway, tangent aside, you can install like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="n">home</span><span class="o">.</span><span class="n">packages</span> <span class="o">=</span> <span class="k">with</span> <span class="n">pkgs</span><span class="p">;</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">  <span class="p">(</span><span class="n">nerdfonts</span><span class="o">.</span><span class="n">override</span> <span class="p">{</span><span class="n">fonts</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;NerdFontsSymbolsOnly&#34;</span><span class="p">];})</span>
</span></span><span class="line"><span class="cl"><span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Next my <a href="https://gitlab.com/hmajid2301/dotfiles/-/blob/79d1ddee586a98816af3f5605de3059d675d58fe/modules/home/styles/stylix/default.nix">Stylix config</a>
looks something like this, for my emoji font:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="n">stylix</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">fonts</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">emoji</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">package</span> <span class="o">=</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">noto-fonts-emoji</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;Noto Color Emoji&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Then finally, I configure Kitty like this, the key lines being the ones defined in the <code>extraConfig</code>. Here we are setting
symbols to map to emoji font and other nerd font symbols to the symbols&rsquo; font.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="n">programs</span><span class="o">.</span><span class="n">kitty</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line hl"><span class="cl">  <span class="n">extraConfig</span> <span class="o">=</span> <span class="s1">&#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">    symbol_map U+e000-U+e00a,U+ea60-U+ebeb,U+e0a0-U+e0c8,U+e0ca,U+e0cc-U+e0d4,U+e200-U+e2a9,U+e300-U+e3e3,U+e5fa-U+e6b1,U+e700-U+e7c5,U+f000-U+f2e0,U+f300-U+f372,U+f400-U+f532,U+f0001-U+f1af0 Symbols Nerd Font Mono
</span></span></span><span class="line"><span class="cl"><span class="s1">    symbol_map U+2600-U+26FF Noto Color Emoji
</span></span></span><span class="line"><span class="cl"><span class="s1">  &#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">settings</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">shell</span> <span class="o">=</span> <span class="s2">&#34;fish&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">window_padding_width</span> <span class="o">=</span> <span class="mi">10</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">scrollback_lines</span> <span class="o">=</span> <span class="mi">10000</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">show_hyperlink_targets</span> <span class="o">=</span> <span class="s2">&#34;yes&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable_audio_bell</span> <span class="o">=</span> <span class="no">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">url_style</span> <span class="o">=</span> <span class="s2">&#34;none&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">underline_hyperlinks</span> <span class="o">=</span> <span class="s2">&#34;never&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">copy_on_select</span> <span class="o">=</span> <span class="s2">&#34;clipboard&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This is what it looks like.</p>
<p><img
        loading="lazy"
        src="/posts/2024-08-06-how-to-configure-kitty-on-nix-to-show-emojis/images/kitty.png"
        type=""
        alt="Kitty Terminal"
        
      /></p>
]]></content:encoded>
    </item>
    
    <item>
      <title>How I Set up BTRFS and LUKS on NixOS Using Disko</title>
      <link>https://haseebmajid.dev/posts/2024-07-30-how-i-setup-btrfs-and-luks-on-nixos-using-disko/</link>
      <pubDate>Tue, 30 Jul 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-07-30-how-i-setup-btrfs-and-luks-on-nixos-using-disko/</guid>
      <description>&lt;p&gt;In this post, I will show you how you can declaratively partition our drives using Nix(OS).&lt;/p&gt;
&lt;details
  class=&#34;notice info&#34;
  open=&#34;true&#34;
&gt;
    &lt;summary class=&#34;notice-title&#34;&gt;TL;DR;&lt;/summary&gt;
  
  We can use a tool called disko to partition our drives declaratively and combine it with NixOS anywhere for a remote
install. Showing an example setting up LUKS encryption with BTRFS file system.
&lt;/details&gt;

&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;re like me, then when you started playing with NixOS, You found yourself constantly reinstalling it and
starting again. Either setting up new machines. Playing around with it in a VM or even realising how to set up LUKS and
hibernate properly. Or even when setting up impermanence.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this post, I will show you how you can declaratively partition our drives using Nix(OS).</p>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">TL;DR;</summary>
  
  We can use a tool called disko to partition our drives declaratively and combine it with NixOS anywhere for a remote
install. Showing an example setting up LUKS encryption with BTRFS file system.
</details>

<h2 id="background">Background</h2>
<p>If you&rsquo;re like me, then when you started playing with NixOS, You found yourself constantly reinstalling it and
starting again. Either setting up new machines. Playing around with it in a VM or even realising how to set up LUKS and
hibernate properly. Or even when setting up impermanence.</p>
<p>Every time I ended using the GUI to reinstall, which was a bit tedious. Then I discovered disko.</p>
<h2 id="disko">Disko</h2>
<p><a href="https://github.com/nix-community/disko">Disko</a> is a fantastic tool. Which allows us to
declaratively declare how to partition our disk(s), in nix configuration.</p>
<h3 id="why">Why</h3>
<p>The great about moving our partition state into code means it far easier to fully automate our installation or setup new
machines with the same partitions.</p>
<p>Previously, I would have to update the hardware-configuration. Nix file manually to change the ID of the drives.
As these UUIDs are unique to each install.</p>
<p>I&rsquo;ll be honest, this has become much less of an issue since my config is very much now stable. But the completion
in me is a lot happier, one I&rsquo;ve automated one step further from setting up a new machine or reinstall the same one.</p>
<h2 id="my-setup">My Setup</h2>
<p>So my setup creates a BTRFS file system and LUKS encryption.</p>
<p>I set up BTRFS, so I can have impermanence, which can be allowed to remove files that we don&rsquo;t have in our persistence
storage. To complete this, I create a persistent sub-volume (think folder). We also want LUKS so until we enter our password or
a secret, our drive(s) will be encrypted. So no one can access our files.</p>
<h3 id="disko-1">Disko</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">disko</span><span class="o">.</span><span class="n">devices</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">disk</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">nvme0n1</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">type</span> <span class="o">=</span> <span class="s2">&#34;disk&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">device</span> <span class="o">=</span> <span class="s2">&#34;/dev/nvme0n1&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">content</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">type</span> <span class="o">=</span> <span class="s2">&#34;gpt&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">          <span class="n">partitions</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">ESP</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">              <span class="n">label</span> <span class="o">=</span> <span class="s2">&#34;boot&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">              <span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;ESP&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">              <span class="n">size</span> <span class="o">=</span> <span class="s2">&#34;512M&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">              <span class="n">type</span> <span class="o">=</span> <span class="s2">&#34;EF00&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">              <span class="n">content</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">type</span> <span class="o">=</span> <span class="s2">&#34;filesystem&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="n">format</span> <span class="o">=</span> <span class="s2">&#34;vfat&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="n">mountpoint</span> <span class="o">=</span> <span class="s2">&#34;/boot&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="n">mountOptions</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                  <span class="s2">&#34;defaults&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="p">];</span>
</span></span><span class="line"><span class="cl">              <span class="p">};</span>
</span></span><span class="line"><span class="cl">            <span class="p">};</span>
</span></span><span class="line"><span class="cl">            <span class="n">luks</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">              <span class="n">size</span> <span class="o">=</span> <span class="s2">&#34;100%&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">              <span class="n">label</span> <span class="o">=</span> <span class="s2">&#34;luks&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">              <span class="n">content</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">type</span> <span class="o">=</span> <span class="s2">&#34;luks&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;cryptroot&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="n">extraOpenArgs</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                  <span class="s2">&#34;--allow-discards&#34;</span>
</span></span><span class="line"><span class="cl">                  <span class="s2">&#34;--perf-no_read_workqueue&#34;</span>
</span></span><span class="line"><span class="cl">                  <span class="s2">&#34;--perf-no_write_workqueue&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="p">];</span>
</span></span><span class="line"><span class="cl">                <span class="c1"># https://0pointer.net/blog/unlocking-luks2-volumes-with-tpm2-fido2-pkcs11-security-hardware-on-systemd-248.html</span>
</span></span><span class="line"><span class="cl">                <span class="n">settings</span> <span class="o">=</span> <span class="p">{</span><span class="n">crypttabExtraOpts</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;fido2-device=auto&#34;</span> <span class="s2">&#34;token-timeout=10&#34;</span><span class="p">];};</span>
</span></span><span class="line"><span class="cl">                <span class="n">content</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                  <span class="n">type</span> <span class="o">=</span> <span class="s2">&#34;btrfs&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                  <span class="n">extraArgs</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;-L&#34;</span> <span class="s2">&#34;nixos&#34;</span> <span class="s2">&#34;-f&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">                  <span class="n">subvolumes</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;/root&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                      <span class="n">mountpoint</span> <span class="o">=</span> <span class="s2">&#34;/&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                      <span class="n">mountOptions</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;subvol=root&#34;</span> <span class="s2">&#34;compress=zstd&#34;</span> <span class="s2">&#34;noatime&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">                    <span class="p">};</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;/home&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                      <span class="n">mountpoint</span> <span class="o">=</span> <span class="s2">&#34;/home&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                      <span class="n">mountOptions</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;subvol=home&#34;</span> <span class="s2">&#34;compress=zstd&#34;</span> <span class="s2">&#34;noatime&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">                    <span class="p">};</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;/nix&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                      <span class="n">mountpoint</span> <span class="o">=</span> <span class="s2">&#34;/nix&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                      <span class="n">mountOptions</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;subvol=nix&#34;</span> <span class="s2">&#34;compress=zstd&#34;</span> <span class="s2">&#34;noatime&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">                    <span class="p">};</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;/persist&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                      <span class="n">mountpoint</span> <span class="o">=</span> <span class="s2">&#34;/persist&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                      <span class="n">mountOptions</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;subvol=persist&#34;</span> <span class="s2">&#34;compress=zstd&#34;</span> <span class="s2">&#34;noatime&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">                    <span class="p">};</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;/log&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                      <span class="n">mountpoint</span> <span class="o">=</span> <span class="s2">&#34;/var/log&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                      <span class="n">mountOptions</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;subvol=log&#34;</span> <span class="s2">&#34;compress=zstd&#34;</span> <span class="s2">&#34;noatime&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">                    <span class="p">};</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;/swap&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                      <span class="n">mountpoint</span> <span class="o">=</span> <span class="s2">&#34;/swap&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                      <span class="n">swap</span><span class="o">.</span><span class="n">swapfile</span><span class="o">.</span><span class="n">size</span> <span class="o">=</span> <span class="s2">&#34;64G&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">};</span>
</span></span><span class="line"><span class="cl">                  <span class="p">};</span>
</span></span><span class="line"><span class="cl">                <span class="p">};</span>
</span></span><span class="line"><span class="cl">              <span class="p">};</span>
</span></span><span class="line"><span class="cl">            <span class="p">};</span>
</span></span><span class="line"><span class="cl">          <span class="p">};</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">fileSystems</span><span class="o">.</span><span class="s2">&#34;/persist&#34;</span><span class="o">.</span><span class="n">neededForBoot</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">fileSystems</span><span class="o">.</span><span class="s2">&#34;/var/log&#34;</span><span class="o">.</span><span class="n">neededForBoot</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Let&rsquo;s break this file down:</p>
<p>Firstly, we only have one drive which we called nvme0n1, which is a 2TB NVMe drive in my pc.
You can find examples on the Disko repo how to partition multiple drives and even how to have in a raid(0) configuration.
For this post, we will keep it simple for now. We need to point it to where our drive exists <code>device = &quot;/dev/nvme0n1&quot;;</code>.</p>
<p>Then we create a boot partition of 512M.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ eza -al /dev/nvme0n1
</span></span><span class="line"><span class="cl">brw-rw---- 259,0 root <span class="m">17</span> Jul 04:52 /dev/nvme0n1
</span></span></code></pre></div><p>Next, we define the LUKS drive, which encrypts everything else, including our swap &ldquo;drive&rdquo;.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">luks</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">size</span> <span class="o">=</span> <span class="s2">&#34;100%&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">label</span> <span class="o">=</span> <span class="s2">&#34;luks&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">content</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">type</span> <span class="o">=</span> <span class="s2">&#34;luks&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;cryptroot&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">extraOpenArgs</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">          <span class="s2">&#34;--allow-discards&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="s2">&#34;--perf-no_read_workqueue&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="s2">&#34;--perf-no_write_workqueue&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">];</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="c1"># https://0pointer.net/blog/unlocking-luks2-volumes-with-tpm2-fido2-pkcs11-security-hardware-on-systemd-248.html</span>
</span></span><span class="line"><span class="cl">      <span class="n">settings</span> <span class="o">=</span> <span class="p">{</span><span class="n">crypttabExtraOpts</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;fido2-device=auto&#34;</span> <span class="s2">&#34;token-timeout=10&#34;</span><span class="p">];};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>We will call this drive <code>cryptroot</code>, when setup we can find it at <code>/dev/mapper</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">/dev/mapper🔒
</span></span><span class="line"><span class="cl">❯ eza -al /dev/mapper
</span></span><span class="line"><span class="cl">crw------- 10,236 root <span class="m">17</span> Jul 04:52 control
</span></span><span class="line"><span class="cl">lrwxrwxrwx      - root <span class="m">17</span> Jul 04:52 cryptroot -&gt; ../dm-0
</span></span></code></pre></div><p>Then in the settings, we add support to allow us to decrypt using Fido, i.e. our YubiKey if we can to set it up
<code>settings = {crypttabExtraOpts = [&quot;fido2-device=auto&quot; &quot;token-timeout=10&quot;];};</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">content</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">type</span> <span class="o">=</span> <span class="s2">&#34;btrfs&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">extraArgs</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;-L&#34;</span> <span class="s2">&#34;nixos&#34;</span> <span class="s2">&#34;-f&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">      <span class="n">subvolumes</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;/root&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">mountpoint</span> <span class="o">=</span> <span class="s2">&#34;/&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">          <span class="n">mountOptions</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;subvol=root&#34;</span> <span class="s2">&#34;compress=zstd&#34;</span> <span class="s2">&#34;noatime&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;/home&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">mountpoint</span> <span class="o">=</span> <span class="s2">&#34;/home&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">          <span class="n">mountOptions</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;subvol=home&#34;</span> <span class="s2">&#34;compress=zstd&#34;</span> <span class="s2">&#34;noatime&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;/nix&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">mountpoint</span> <span class="o">=</span> <span class="s2">&#34;/nix&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">          <span class="n">mountOptions</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;subvol=nix&#34;</span> <span class="s2">&#34;compress=zstd&#34;</span> <span class="s2">&#34;noatime&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;/persist&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">mountpoint</span> <span class="o">=</span> <span class="s2">&#34;/persist&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">          <span class="n">mountOptions</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;subvol=persist&#34;</span> <span class="s2">&#34;compress=zstd&#34;</span> <span class="s2">&#34;noatime&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;/log&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">mountpoint</span> <span class="o">=</span> <span class="s2">&#34;/var/log&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">          <span class="n">mountOptions</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;subvol=log&#34;</span> <span class="s2">&#34;compress=zstd&#34;</span> <span class="s2">&#34;noatime&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;/swap&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">mountpoint</span> <span class="o">=</span> <span class="s2">&#34;/swap&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">          <span class="n">swap</span><span class="o">.</span><span class="n">swapfile</span><span class="o">.</span><span class="n">size</span> <span class="o">=</span> <span class="s2">&#34;64G&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">  <span class="n">fileSystems</span><span class="o">.</span><span class="s2">&#34;/persist&#34;</span><span class="o">.</span><span class="n">neededForBoot</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">fileSystems</span><span class="o">.</span><span class="s2">&#34;/var/log&#34;</span><span class="o">.</span><span class="n">neededForBoot</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Then the final part defines the different sub-volumes we want. Which we can just think of as folders on our system.
We label this partition as <code>nixos</code> as well, so we can refer to it via this label (hence the <code>-L</code> flag passed to it).</p>
<pre tabindex="0"><code>type = &#34;btrfs&#34;;
extraArgs = [&#34;-L&#34; &#34;nixos&#34; &#34;-f&#34;];
</code></pre><p>We also want to create a BTRFS file system on our main partition here, simply so that we can revert to a blank state
and copy files over from our persist sub-volume. So we can do, impermanence.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ df -h
</span></span><span class="line"><span class="cl">Filesystem      Size  Used Avail Use% Mounted on
</span></span><span class="line"><span class="cl">/dev/dm-0       1.9T  846G 1006G  46% /
</span></span><span class="line"><span class="cl">/dev/dm-0       1.9T  846G 1006G  46% /nix
</span></span><span class="line"><span class="cl">/dev/dm-0       1.9T  846G 1006G  46% /persist
</span></span><span class="line"><span class="cl">/dev/dm-0       1.9T  846G 1006G  46% /var/log
</span></span><span class="line"><span class="cl">tmpfs           7.7G   15M  7.7G   1% /run
</span></span><span class="line"><span class="cl">devtmpfs        1.6G     <span class="m">0</span>  1.6G   0% /dev
</span></span><span class="line"><span class="cl">tmpfs            16G   23M   16G   1% /dev/shm
</span></span><span class="line"><span class="cl">tmpfs            16G  1.3M   16G   1% /run/wrappers
</span></span><span class="line"><span class="cl">efivarfs        148K   93K   51K  65% /sys/firmware/efi/efivars
</span></span><span class="line"><span class="cl">/dev/dm-0       1.9T  846G 1006G  46% /home
</span></span><span class="line"><span class="cl">/dev/dm-0       1.9T  846G 1006G  46% /swap
</span></span><span class="line"><span class="cl">/dev/nvme0n1p1  511M  236M  276M  47% /boot
</span></span></code></pre></div><p>If we call this file <code>disks.nix</code> we can refer to it in our main configuration like so. Similar to how we refer to
hardware-configuration.nix.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">pkgs</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">lib</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">imports</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="sr">./hardware-configuration.nix</span>
</span></span><span class="line"><span class="cl">    <span class="sr">./disks.nix</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h2 id="hibernate">Hibernate</h2>
<p>To enable your PC to hibernate using your swap, you can add the following boot configuration.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">boot</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">kernelParams</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;resume_offset=533760&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="n">resumeDevice</span> <span class="o">=</span> <span class="s2">&#34;/dev/disk/by-label/nixos&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>We need to add an offset because the swap is part of our main partition, rather than being its own partition.
Which is how I used to set it up. But I believe if you are also using a 2TB drive, you will have the same offset.</p>
<p>To figure out the exact off set, you can follow the
<a href="https://wiki.archlinux.org/title/Power_management/Suspend_and_hibernate#Acquire_swap_file_offset">Arch wiki here</a>.</p>
<h2 id="fido2">Fido2</h2>
<p>If you want to decrypt your LUKS drive with a YubiKey, you can do something like:
<code>sudo -E -s systemd-cryptenroll --fido2-device=auto  /dev/nvme0n1p2</code>. Then you need to press the button on your YubiKey
to register. Then during LUKS decryption you can use your YubiKey. However, I think from memory this can cause
decryption to be a bit slower as it waits for the YubiKey.</p>
<p>You can read more about it <a href="https://wiki.archlinux.org/title/Systemd-cryptenroll">here</a>.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/dotfiles/-/blob/73a83021c0747aaeb6d104b3b729513b2ab02d4d/systems/x86_64-linux/workstation/disks.nix">My config for my main machine</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How I Configured Zellij Status Bar</title>
      <link>https://haseebmajid.dev/posts/2024-07-26-how-i-configured-zellij-status-bar/</link>
      <pubDate>Fri, 26 Jul 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-07-26-how-i-configured-zellij-status-bar/</guid>
      <description>&lt;p&gt;As some of you may know, I moved to Zellij a few months ago from tmux. In this post, I will show you have I configured
the zellij status bar in nix. So it acts more like how my config did in tmux.&lt;/p&gt;
&lt;p&gt;My current Zellij status bar.&lt;/p&gt;
&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2024-07-26-how-i-configured-zellij-status-bar/images/status.png&#34;
        type=&#34;&#34;
        alt=&#34;My current status bar&#34;
        
      /&gt;&lt;/p&gt;
&lt;h2 id=&#34;optional-background&#34;&gt;(Optional) Background&lt;/h2&gt;
&lt;p&gt;A bit of background, which you can skip if you&amp;rsquo;d like.&lt;/p&gt;
&lt;h3 id=&#34;what-is-zellij&#34;&gt;What is zellij?&lt;/h3&gt;
&lt;p&gt;Some of you may be wondering what is zellij/tmux. They are what we call multiplexers. Essentially, our terminals
now can have a client server architecture. So we can kill our terminal but, our session will still be alive.
Which makes it great for jumping between projects if you are using say Neovim. When I first moved to tmux, I found
it is great because now I don&amp;rsquo;t need 4/5 VS Code instances open. Instead, I could use a script to jump between my projects,
and tmux would keep the session alive, so just how I left the project. Zellij does basically the same thing for me.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>As some of you may know, I moved to Zellij a few months ago from tmux. In this post, I will show you have I configured
the zellij status bar in nix. So it acts more like how my config did in tmux.</p>
<p>My current Zellij status bar.</p>
<p><img
        loading="lazy"
        src="/posts/2024-07-26-how-i-configured-zellij-status-bar/images/status.png"
        type=""
        alt="My current status bar"
        
      /></p>
<h2 id="optional-background">(Optional) Background</h2>
<p>A bit of background, which you can skip if you&rsquo;d like.</p>
<h3 id="what-is-zellij">What is zellij?</h3>
<p>Some of you may be wondering what is zellij/tmux. They are what we call multiplexers. Essentially, our terminals
now can have a client server architecture. So we can kill our terminal but, our session will still be alive.
Which makes it great for jumping between projects if you are using say Neovim. When I first moved to tmux, I found
it is great because now I don&rsquo;t need 4/5 VS Code instances open. Instead, I could use a script to jump between my projects,
and tmux would keep the session alive, so just how I left the project. Zellij does basically the same thing for me.</p>
<h3 id="why-zellij">Why zellij?</h3>
<p>A few reasons, but the main one being I really, really like the floating panes. I know you can probably achieve the
same just using normal panes and just swapping between them, but I really like the idea of having floating panes and still
have what I was working on just behind the pane. It makes harder for me to forget or lost context on what I was doing.</p>
<p>Zellij, is modal a bit like vim, depending on the mode your keyboard shortcuts can change, i.e. scroll mode, tab or pane
mode. At first, I didn&rsquo;t like this, but now I&rsquo;ve come to find it pretty useful.</p>
<h2 id="status-bar">Status Bar</h2>
<p>I don&rsquo;t really like the default status bar in zellij, it is very useful at the beginning when you are learning the
keyboard shortcuts. I soon moved over the minimal status bar and moved it to the top, so I could tell it apart easier
from my Neovim status bar. However, I didn&rsquo;t like the fact I couldn&rsquo;t change the colour, it would always be green
and didn&rsquo;t fit the rest of my theme (catppuccin mocha).</p>
<p><img
        loading="lazy"
        src="/posts/2024-07-26-how-i-configured-zellij-status-bar/images/new.png"
        type=""
        alt="New status bar"
        
      /></p>
<p>So what we will now do is configure the status bar, I found this <a href="https://github.com/dj95/zjstatus">plugin</a>.
Which allows us to customise our status bar, I wanted mine to look more like how I had it in my tmux configuration.</p>
<p>Here is how the old status bar looked.</p>
<p><img
        loading="lazy"
        src="/posts/2024-07-26-how-i-configured-zellij-status-bar/images/old.png"
        type=""
        alt="Old status bar"
        
      /></p>
<h3 id="nix">Nix</h3>
<p>I have configured zellij using Nix via home-manager. So the rest of the post will show you how I did that. It should be
easily adjusted for other setups.</p>
<p>I will show you how I configured it in Nix, though it should be easy enough to work out the non-nix version. If you are
using Nix flakes, you can add it as an input to your flake.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">zjstatus</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;github:dj95/zjstatus&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Then add the following overlay, since I&rsquo;m using the [snowfall-lib] I can do something like <code>overlays/zjstatus/default.nix</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="n">inputs</span><span class="o">,</span> <span class="o">...</span><span class="p">}:</span> <span class="n">final</span><span class="p">:</span> <span class="n">prev</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">zjstatus</span> <span class="o">=</span> <span class="n">inputs</span><span class="o">.</span><span class="n">zjstatus</span><span class="o">.</span><span class="n">packages</span><span class="o">.</span><span class="si">${</span><span class="n">prev</span><span class="o">.</span><span class="n">system</span><span class="si">}</span><span class="o">.</span><span class="n">default</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Next create a module for your zellij config, in my case I put in a file called <code>home/cli/multiplexers/zellij/default.nix</code>.</p>
<p>Here, we enable zellij (using the home-manager options).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">xdg</span><span class="o">.</span><span class="n">configFile</span><span class="o">.</span><span class="s2">&#34;zellij/config.kdl&#34;</span><span class="o">.</span><span class="n">source</span> <span class="o">=</span> <span class="sr">./config.kdl</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">programs</span><span class="o">.</span><span class="n">zellij</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Where my <code>config.kdl</code> looks something like this, my <a href="https://gitlab.com/hmajid2301/dotfiles/-/blob/e7f96144fbb45a99173d21079f3bfbfd20eb7c91/modules/home/cli/multiplexers/zellij/config.kdl#L8">specific key bindings</a>. Just to be more Alt based, so it doesn&rsquo;t
clash with my Neovim bindings.</p>
<pre tabindex="0"><code class="language-kdl" data-lang="kdl">theme &#34;catppuccin-mocha&#34;
pane_frames false
simplified_ui true
default_shell &#34;fish&#34;
copy_on_select true

// If you&#39;d like to override the default keybindings completely, be sure to change &#34;keybinds&#34; to &#34;keybinds clear-defaults=true&#34;
keybinds {

}
</code></pre><p>Now onto the real meat and potatoes of the post, my default layout, you can see my full <a href="https://gitlab.com/hmajid2301/dotfiles/-/blob/e7f96144fbb45a99173d21079f3bfbfd20eb7c91/modules/home/cli/multiplexers/zellij/default.nix">config here</a>.
Also note how we reference the plugin using the nix package here <code>plugin location=&quot;file://${pkgs.zjstatus}/bin/zjstatus.wasm&quot;</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">xdg</span><span class="o">.</span><span class="n">configFile</span><span class="o">.</span><span class="s2">&#34;zellij/layouts/default.kdl&#34;</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="s1">&#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">      layout {
</span></span></span><span class="line"><span class="cl"><span class="s1">          default_tab_template {
</span></span></span><span class="line"><span class="cl"><span class="s1">              pane size=2 borderless=true {
</span></span></span><span class="line"><span class="cl"><span class="s1">                  plugin location=&#34;file://</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">zjstatus</span><span class="si">}</span><span class="s1">/bin/zjstatus.wasm&#34; {
</span></span></span><span class="line"><span class="cl"><span class="s1">                      format_left   &#34;{mode}#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base00</span><span class="si">}</span><span class="s1">] {tabs}&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">                      format_center &#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">                      format_right  &#34;#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base00</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base0D</span><span class="si">}</span><span class="s1">]#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base0D</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base01</span><span class="si">}</span><span class="s1">,bold] #[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base02</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base05</span><span class="si">}</span><span class="s1">,bold] {session} #[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base03</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base05</span><span class="si">}</span><span class="s1">,bold]&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">                      format_space  &#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">                      format_hide_on_overlength &#34;true&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">                      format_precedence &#34;crl&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="cl"><span class="s1">                      border_enabled  &#34;false&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">                      border_char     &#34;─&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">                      border_format   &#34;#[fg=#6C7086]{char}&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">                      border_position &#34;top&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="cl"><span class="s1">                      mode_normal        &#34;#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base0B</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base02</span><span class="si">}</span><span class="s1">,bold] NORMAL#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base03</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base0B</span><span class="si">}</span><span class="s1">]█&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">                      mode_locked        &#34;#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base04</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base02</span><span class="si">}</span><span class="s1">,bold] LOCKED #[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base03</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base04</span><span class="si">}</span><span class="s1">]█&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">                      mode_resize        &#34;#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base08</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base02</span><span class="si">}</span><span class="s1">,bold] RESIZE#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base03</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base08</span><span class="si">}</span><span class="s1">]█&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">                      mode_pane          &#34;#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base0D</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base02</span><span class="si">}</span><span class="s1">,bold] PANE#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base03</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base0D</span><span class="si">}</span><span class="s1">]█&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">                      mode_tab           &#34;#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base07</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base02</span><span class="si">}</span><span class="s1">,bold] TAB#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base03</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base07</span><span class="si">}</span><span class="s1">]█&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">                      mode_scroll        &#34;#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base0A</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base02</span><span class="si">}</span><span class="s1">,bold] SCROLL#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base03</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base0A</span><span class="si">}</span><span class="s1">]█&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">                      mode_enter_search  &#34;#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base0D</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base02</span><span class="si">}</span><span class="s1">,bold] ENT-SEARCH#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base03</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base0D</span><span class="si">}</span><span class="s1">]█&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">                      mode_search        &#34;#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base0D</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base02</span><span class="si">}</span><span class="s1">,bold] SEARCHARCH#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base03</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base0D</span><span class="si">}</span><span class="s1">]█&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">                      mode_rename_tab    &#34;#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base07</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base02</span><span class="si">}</span><span class="s1">,bold] RENAME-TAB#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base03</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base07</span><span class="si">}</span><span class="s1">]█&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">                      mode_rename_pane   &#34;#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base0D</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base02</span><span class="si">}</span><span class="s1">,bold] RENAME-PANE#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base03</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base0D</span><span class="si">}</span><span class="s1">]█&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">                      mode_session       &#34;#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base0E</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base02</span><span class="si">}</span><span class="s1">,bold] SESSION#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base03</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base0E</span><span class="si">}</span><span class="s1">]█&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">                      mode_move          &#34;#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base0F</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base02</span><span class="si">}</span><span class="s1">,bold] MOVE#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base03</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base0F</span><span class="si">}</span><span class="s1">]█&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">                      mode_prompt        &#34;#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base0D</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base02</span><span class="si">}</span><span class="s1">,bold] PROMPT#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base03</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base0D</span><span class="si">}</span><span class="s1">]█&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">                      mode_tmux          &#34;#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base09</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base02</span><span class="si">}</span><span class="s1">,bold] TMUX#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base03</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base09</span><span class="si">}</span><span class="s1">]█&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="cl"><span class="s1">                      // formatting for inactive tabs
</span></span></span><span class="line"><span class="cl"><span class="s1">                      tab_normal              &#34;#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base03</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base0D</span><span class="si">}</span><span class="s1">]█#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base0D</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base02</span><span class="si">}</span><span class="s1">,bold]{index} #[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base02</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base05</span><span class="si">}</span><span class="s1">,bold] {name}{floating_indicator}#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base03</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base02</span><span class="si">}</span><span class="s1">,bold]█&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">                      tab_normal_fullscreen   &#34;#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base03</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base0D</span><span class="si">}</span><span class="s1">]█#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base0D</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base02</span><span class="si">}</span><span class="s1">,bold]{index} #[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base02</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base05</span><span class="si">}</span><span class="s1">,bold] {name}{fullscreen_indicator}#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base03</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base02</span><span class="si">}</span><span class="s1">,bold]█&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">                      tab_normal_sync         &#34;#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base03</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base0D</span><span class="si">}</span><span class="s1">]█#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base0D</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base02</span><span class="si">}</span><span class="s1">,bold]{index} #[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base02</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base05</span><span class="si">}</span><span class="s1">,bold] {name}{sync_indicator}#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base03</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base02</span><span class="si">}</span><span class="s1">,bold]█&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="cl"><span class="s1">                      // formatting for the current active tab
</span></span></span><span class="line"><span class="cl"><span class="s1">                      tab_active              &#34;#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base03</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base09</span><span class="si">}</span><span class="s1">]█#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base09</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base02</span><span class="si">}</span><span class="s1">,bold]{index} #[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base02</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base05</span><span class="si">}</span><span class="s1">,bold] {name}{floating_indicator}#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base03</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base02</span><span class="si">}</span><span class="s1">,bold]█&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">                      tab_active_fullscreen   &#34;#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base03</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base09</span><span class="si">}</span><span class="s1">]█#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base09</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base02</span><span class="si">}</span><span class="s1">,bold]{index} #[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base02</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base05</span><span class="si">}</span><span class="s1">,bold] {name}{fullscreen_indicator}#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base03</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base02</span><span class="si">}</span><span class="s1">,bold]█&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">                      tab_active_sync         &#34;#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base03</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base09</span><span class="si">}</span><span class="s1">]█#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base09</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base02</span><span class="si">}</span><span class="s1">,bold]{index} #[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base02</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base05</span><span class="si">}</span><span class="s1">,bold] {name}{sync_indicator}#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base03</span><span class="si">}</span><span class="s1">,fg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base02</span><span class="si">}</span><span class="s1">,bold]█&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="cl"><span class="s1">                      // separator between the tabs
</span></span></span><span class="line"><span class="cl"><span class="s1">                      tab_separator           &#34;#[bg=#</span><span class="si">${</span><span class="n">colors</span><span class="o">.</span><span class="n">base00</span><span class="si">}</span><span class="s1">] &#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="cl"><span class="s1">                      // indicators
</span></span></span><span class="line"><span class="cl"><span class="s1">                      tab_sync_indicator       &#34; &#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">                      tab_fullscreen_indicator &#34; 󰊓&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">                      tab_floating_indicator   &#34; 󰹙&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="cl"><span class="s1">                      command_git_branch_command     &#34;git rev-parse --abbrev-ref HEAD&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">                      command_git_branch_format      &#34;#[fg=blue] {stdout} &#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">                      command_git_branch_interval    &#34;10&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">                      command_git_branch_rendermode  &#34;static&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="cl"><span class="s1">                      datetime        &#34;#[fg=#6C7086,bold] {format} &#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">                      datetime_format &#34;%A, %d %b %Y %H:%M&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">                      datetime_timezone &#34;Europe/London&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">                  }
</span></span></span><span class="line"><span class="cl"><span class="s1">              }
</span></span></span><span class="line"><span class="cl"><span class="s1">              children
</span></span></span><span class="line"><span class="cl"><span class="s1">          }
</span></span></span><span class="line"><span class="cl"><span class="s1">      }
</span></span></span><span class="line"><span class="cl"><span class="s1">    &#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>I am using stylix to theme my apps <code>inherit (config.lib.stylix) colors;</code>, which uses the base16 colours so instead of
hardcoding colours, I reference <a href="https://github.com/tinted-theming/base16-schemes/blob/main/catppuccin-mocha.yaml">these colours</a>.</p>
<p>Essentially, all we do on the left-hand side we use straight &ldquo;█&rdquo; and on the right-hand side I use the circular &ldquo;&rdquo;.
My current active tab is orange and inactive is blue, where the colour of the text is white.</p>
<p>Then the different colours for the different modes, i.e. normal, tab, locked.</p>
<p><img
        loading="lazy"
        src="/posts/2024-07-26-how-i-configured-zellij-status-bar/images/normal.png"
        type=""
        alt="Normal"
        
      />
<img
        loading="lazy"
        src="/posts/2024-07-26-how-i-configured-zellij-status-bar/images/tab.png"
        type=""
        alt="tab"
        
      />
<img
        loading="lazy"
        src="/posts/2024-07-26-how-i-configured-zellij-status-bar/images/locked.png"
        type=""
        alt="locked"
        
      /></p>
<h2 id="auto-rename-tabs">Auto rename tabs</h2>
<p>I also wanted to auto-rename the tabs in zellij like happened in tmux. Depending on the process running, the tab name
would change to that.</p>
<p>So, with some help from ChatGPT and a <a href="https://old.reddit.com/r/zellij/comments/10skez0/does_zellij_support_changing_tabs_name_according/">Reddit thread</a>.  I came up with the following:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fish" data-lang="fish"><span class="line"><span class="cl"><span class="k">if</span> <span class="nb">status </span>is-interactive
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nb">type</span> <span class="na">-q</span> zellij
</span></span><span class="line"><span class="cl">        <span class="c"># Update the zellij tab name with the current process name or pwd.
</span></span></span><span class="line"><span class="cl"><span class="c"></span>        <span class="k">function</span> <span class="nf">zellij_tab_name_update_pre</span> <span class="na">--on-event</span> fish_preexec
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="k">set</span> <span class="na">-q</span> <span class="nv">ZELLIJ</span>
</span></span><span class="line"><span class="cl">                <span class="k">set</span> <span class="na">-l</span> <span class="nv">cmd_line</span> <span class="o">(</span><span class="nb">string </span>split <span class="s2">&#34; &#34;</span> <span class="na">-- </span><span class="nv">$argv</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">                <span class="k">set</span> <span class="na">-l</span> <span class="nv">process_name</span> <span class="nv">$cmd_line</span><span class="o">[</span><span class="m">1</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="k">test</span> <span class="na">-n</span> <span class="s2">&#34;</span><span class="nv">$process_name</span><span class="s2">&#34;</span> <span class="na">-a</span> <span class="s2">&#34;</span><span class="nv">$process_name</span><span class="s2">&#34;</span> !<span class="o">=</span> <span class="s2">&#34;z&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="nb">command </span>nohup zellij action rename-tab <span class="nv">$process_name</span> <span class="o">&gt;</span>/dev/null <span class="m">2</span><span class="o">&gt;&amp;</span><span class="m">1</span>
</span></span><span class="line"><span class="cl">                <span class="k">end</span>
</span></span><span class="line"><span class="cl">            <span class="k">end</span>
</span></span><span class="line"><span class="cl">        <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">function</span> <span class="nf">zellij_tab_name_update_post</span> <span class="na">--on-event</span> fish_postexec
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="k">set</span> <span class="na">-q</span> <span class="nv">ZELLIJ</span>
</span></span><span class="line"><span class="cl">                <span class="k">set</span> <span class="na">-l</span> <span class="nv">cmd_line</span> <span class="o">(</span><span class="nb">string </span>split <span class="s2">&#34; &#34;</span> <span class="na">-- </span><span class="nv">$argv</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">                <span class="k">set</span> <span class="na">-l</span> <span class="nv">process_name</span> <span class="nv">$cmd_line</span><span class="o">[</span><span class="m">1</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="k">test</span> <span class="s2">&#34;</span><span class="nv">$process_name</span><span class="s2">&#34;</span> <span class="o">=</span> <span class="s2">&#34;z&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="nb">command </span>nohup zellij action rename-tab <span class="o">(</span><span class="nb">prompt_pwd</span><span class="o">)</span> <span class="o">&gt;</span>/dev/null <span class="m">2</span><span class="o">&gt;&amp;</span><span class="m">1</span>
</span></span><span class="line"><span class="cl">                <span class="k">end</span>
</span></span><span class="line"><span class="cl">            <span class="k">end</span>
</span></span><span class="line"><span class="cl">        <span class="k">end</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span></code></pre></div><p>Since I use <code>zoxide</code>, I don&rsquo;t want to see just z, when I change directory; hence I have the code to ignore.
It is possible we can simplify this, but it works for now. However, one issue I did note was that the process running
the floating pane would overwrite the main process, i.e. running Neovim in a tab I would like to see <code>nvim</code> but since
I ran git, it now shows git.</p>
<p><img
        loading="lazy"
        src="/posts/2024-07-26-how-i-configured-zellij-status-bar/images/pwd-status-bar.png"
        type=""
        alt="z status bar"
        
      />
<img
        loading="lazy"
        src="/posts/2024-07-26-how-i-configured-zellij-status-bar/images/git-status-bar.png"
        type=""
        alt="git status bar"
        
      /></p>
<h4 id="nix-1">Nix</h4>
<p>Since I am also configuring fish shell with nix, I added the above to the <code>interactiveShellInit</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">programs</span><span class="o">.</span><span class="n">fish</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">interactiveShellInit</span> <span class="o">=</span> <span class="s1">&#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">      &#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://github.com/dj95/zjstatus/discussions/44#discussioncomment-9641187">Thanks for inspiration</a></li>
<li><a href="https://old.reddit.com/r/zellij/comments/10skez0/does_zellij_support_changing_tabs_name_according/">Reddit thread auto-rename</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Part 6: Fish Shell as Part of Your Development Workflow</title>
      <link>https://haseebmajid.dev/posts/2024-07-01-part-6-fish-shell-as-part-of-your-development-workflow/</link>
      <pubDate>Mon, 01 Jul 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-07-01-part-6-fish-shell-as-part-of-your-development-workflow/</guid>
      <description>&lt;p&gt;After a long break, I am again writing about my workflow. The big issue I have with this series is that my workflow is
kind of always in flux and changing. Which maybe is a bad thing. I just need to pick some tools and stick to it.
Rather than trying four different terminal emulators, i.e. alacritty, kitty, foot and wezterm.&lt;/p&gt;
&lt;p&gt;However, over the last ~5ish years, one thing that has remained pretty constant is my use of &lt;a href=&#34;https://github.com/fish-shell/fish-shell&#34;&gt;fish&lt;/a&gt; shell.
In this post, I will go over why I stick with fish, and how I have set it up for my own use case.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>After a long break, I am again writing about my workflow. The big issue I have with this series is that my workflow is
kind of always in flux and changing. Which maybe is a bad thing. I just need to pick some tools and stick to it.
Rather than trying four different terminal emulators, i.e. alacritty, kitty, foot and wezterm.</p>
<p>However, over the last ~5ish years, one thing that has remained pretty constant is my use of <a href="https://github.com/fish-shell/fish-shell">fish</a> shell.
In this post, I will go over why I stick with fish, and how I have set it up for my own use case.</p>
<h2 id="caveat">Caveat</h2>
<p>One big downside to fish shell is that it is not POSIX complaint. Without going to into details, what that basically
means is that some commands/syntax of other shells, mainly bash and zsh. It is not fully compatible with fish shell.</p>
<p>So, sometimes if you are copying a command on the internet, it may not work, or fish will throw an error. There are
some ways around this, though. But it is important to realise that this could cause issues. For this reason, some people
don&rsquo;t really think fish shell is a real shell. Which I disagree with myself.</p>
<h2 id="why-fish-shell">Why fish shell?</h2>
<p>So why fish shell? Well, it&rsquo;s a good question., I like most people, started using bash when I started my
programming journey. Which happened to be around the same time I also started learning Linux (at university).</p>
<p>Bash was mostly fine, I mean I remember when I learnt you could press tab to auto-complete commands, mind blown for sure.
I think I first saw fish shell, when I was randomly perusing through random YouTube videos about Linux. I remember
finding the joke on their website about it being a shell designed for the 90s, hilarious.</p>
<p>Until this video, I was pretty happy with bash, even using the default prompt, but seeing how other people customised
their shell, I was intrigued. The main feature of the shell that caught my attention was the colouring of commands when
a binary/script exists. If a binary is not found in your PATH, it would be red else blue, for example.</p>
<p><img
        loading="lazy"
        src="/posts/2024-07-01-part-6-fish-shell-as-part-of-your-development-workflow/images/fish-error.png"
        type=""
        alt="Fish showing error"
        
      />
<img
        loading="lazy"
        src="/posts/2024-07-01-part-6-fish-shell-as-part-of-your-development-workflow/images/fish-fine.png"
        type=""
        alt="Fish now showing error"
        
      /></p>
<p>One other thing I liked is it shows you a command you wish to run (nothing to do with AI). But the last similar
command, you can see in the image above with <code>ls</code> and <code>lscpi</code>.</p>
<p>Essentially, I like that fish shell feels fast to me, and has a bunch of features built in I think are really cool.</p>
<h3 id="zsh">ZSH</h3>
<p>I found out later that you could get most of these features in ZSH as well. Using 3/4 plugins, and make it just like
fish shell. This was a few years ago and the plugins may have improved however at the time, I noticed my shell was a
big slower. Enough where it bothered me, but maybe fine for some people. I also noticed, the colour <code>zsh-syntax-highlighting</code>.
It was as good as the built-in fish one.</p>
<p>Again, just something to keep in mind, you can get a &ldquo;fish like&rdquo; experience on ZSH and potentially the plugins are better.
You don&rsquo;t notice any difference.</p>
<h2 id="how-i-configured-it">How I configured it?</h2>
<p><a href="https://gitlab.com/hmajid2301/dotfiles/-/blob/d62db156dc89ec6ea3b30f479fa7197c8bcafd4a/modules/home/cli/shells/fish/default.nix">My fish config, at the time of writing this</a>.</p>
<p>As you can probably imagine, I use nix to configure my fish shell. Using a similar format to my other modules;</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">pkgs</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">lib</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">config</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">host</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}:</span>
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="n">lib</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="n">lib</span><span class="o">.</span><span class="n">nixicle</span><span class="p">;</span> <span class="k">let</span>
</span></span><span class="line"><span class="cl">  <span class="n">cfg</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">cli</span><span class="o">.</span><span class="n">shells</span><span class="o">.</span><span class="n">fish</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">in</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">options</span><span class="o">.</span><span class="n">cli</span><span class="o">.</span><span class="n">shells</span><span class="o">.</span><span class="n">fish</span> <span class="o">=</span> <span class="k">with</span> <span class="n">types</span><span class="p">;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="n">mkBoolOpt</span> <span class="no">false</span> <span class="s2">&#34;enable fish shell&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line hl"><span class="cl">  <span class="n">config</span> <span class="o">=</span> <span class="n">mkIf</span> <span class="n">cfg</span><span class="o">.</span><span class="n">enable</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">To</span> <span class="n">use</span> <span class="n">it</span> <span class="n">in</span> <span class="n">my</span> <span class="n">config</span> <span class="n">I</span> <span class="n">enable</span> <span class="n">it</span> <span class="n">like</span> <span class="n">so</span> <span class="err">`</span><span class="n">cli</span><span class="o">.</span><span class="n">shells</span><span class="o">.</span><span class="n">fish</span><span class="o">.</span><span class="n">enable</span> <span class="err">=</span> <span class="no">true</span><span class="err">`</span><span class="p">;</span>
</span></span></code></pre></div><h3 id="shell-setup">Shell Setup</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">interactiveShellInit</span> <span class="o">=</span> <span class="s1">&#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">        </span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">nix-your-shell</span><span class="si">}</span><span class="s1">/bin/nix-your-shell --nom fish | source
</span></span></span><span class="line"><span class="cl"><span class="s1">    &#39;&#39;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>We use this package so that when we run certain nix commands, we stay within the fish shell. For example, <code>nix-shell -p neovim</code>.
Normally, that command would drop us into a bash shell. It also remembers the fish shell&rsquo;s history we were just in.
For example, when we press the up key to go back to previous command. We also use <code>--nom</code> so we have the nix output monitor.
Which provides a nicer output when building packages.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">interactiveShellInit</span> <span class="o">=</span> <span class="s1">&#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">        fish_vi_key_bindings
</span></span></span><span class="line"><span class="cl"><span class="s1">        set fish_cursor_default     block      blink
</span></span></span><span class="line"><span class="cl"><span class="s1">        set fish_cursor_insert      line       blink
</span></span></span><span class="line"><span class="cl"><span class="s1">        set fish_cursor_replace_one underscore blink
</span></span></span><span class="line"><span class="cl"><span class="s1">        set fish_cursor_visual      block
</span></span></span><span class="line"><span class="cl"><span class="s1">        bind --mode insert --sets-mode default jk repaint
</span></span></span><span class="line"><span class="cl"><span class="s1">    &#39;&#39;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>One really cool thing you can do in most shells is enabling vim mode, so you can use vim commands to navigate. For example,
we can select things using visual mode. Or jumping to the start of the line <code>^</code>.</p>
<p>The commands above just set up what the cursor looks like in different modes, i.e. block cursor in visual mode.
Then we also have a command <code>jk</code>, to jump into normal mode. Acts like an escape. Though I use this a lot less now
I have a split keyboard and the escape key is on my thumb cluster.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">interactiveShellInit</span> <span class="o">=</span> <span class="s1">&#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">        set -Ux fifc_editor nvim
</span></span></span><span class="line"><span class="cl"><span class="s1">        set -U fifc_keybinding \cx
</span></span></span><span class="line"><span class="cl"><span class="s1">        bind \cx _fifc
</span></span></span><span class="line"><span class="cl"><span class="s1">        bind -M insert \cx _fifc
</span></span></span><span class="line"><span class="cl"><span class="s1">    &#39;&#39;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>In another block, I have the following, so I can use <code>fifc</code> with <code>ctrl + x</code>, instead of tab complete, which I found annoying.
You can read about <code>fifc</code> in the plugins section below.</p>
<h3 id="plugins">Plugins</h3>
<p>Some of the plugins I use!</p>
<ul>
<li><code>bass</code>: Allows us to run bash utilities in fish shell</li>
<li><code>fzf-fish</code>: Integrates fish shell with FZF, really useful for improved command history search. <code>ctrl + r</code></li>
<li><code>git-abbr</code> &amp; <code>kubectl-abbr</code>: Abbreviations available to me, i.e. <code>gco</code> -&gt; <code>git checkout</code></li>
<li><code>fifc</code>: Use FZF to auto-complete CLI arguments/options
<ul>
<li><code>nix.fish</code>: Very useful when using with nix shell, would usually change to bash now keeps in fish and remembers the history i.e <code>nix-shell -p neovim</code></li>
</ul>
</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">plugins</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;bass&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="k">inherit</span> <span class="p">(</span><span class="n">pkgs</span><span class="o">.</span><span class="n">fishPlugins</span><span class="o">.</span><span class="n">bass</span><span class="p">)</span> <span class="n">src</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;fzf-fish&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="k">inherit</span> <span class="p">(</span><span class="n">pkgs</span><span class="o">.</span><span class="n">fishPlugins</span><span class="o">.</span><span class="n">fzf-fish</span><span class="p">)</span> <span class="n">src</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;fifc&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="k">inherit</span> <span class="p">(</span><span class="n">pkgs</span><span class="o">.</span><span class="n">fishPlugins</span><span class="o">.</span><span class="n">fifc</span><span class="p">)</span> <span class="n">src</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;kubectl-abbr&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">src</span> <span class="o">=</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">fetchFromGitHub</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">owner</span> <span class="o">=</span> <span class="s2">&#34;lewisacidic&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">repo</span> <span class="o">=</span> <span class="s2">&#34;fish-kubectl-abbr&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">rev</span> <span class="o">=</span> <span class="s2">&#34;161450ab83da756c400459f4ba8e8861770d930c&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">sha256</span> <span class="o">=</span> <span class="s2">&#34;sha256-iKNaD0E7IwiQZ+7pTrbPtrUcCJiTcVpb9ksVid1J6A0=&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;git-abbr&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">src</span> <span class="o">=</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">fetchFromGitHub</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">owner</span> <span class="o">=</span> <span class="s2">&#34;lewisacidic&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">repo</span> <span class="o">=</span> <span class="s2">&#34;fish-git-abbr&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">rev</span> <span class="o">=</span> <span class="s2">&#34;dc590a5b9d9d2095f95f7d90608b48e55bea0b0e&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">sha256</span> <span class="o">=</span> <span class="s2">&#34;sha256-6z3Wr2t8CP85xVEp6UCYaM2KC9PX4MDyx19f/wjHkb0=&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="abbreviations">Abbreviations</h3>
<p>Abbreviations are like aliases except when you are done, it will turn itself to the actual command, i.e. you could alias
<code>gco</code> which is <code>git checkout</code>. In your shell history, it will show up as <code>gco</code>. However, with abbreviations after you
are down typing the abbreviation it will automagically become a command, i.e. <code>gco</code> -&gt; <code>git checkout</code>. So this will
show up in your shell history as <code>git checkout</code>. Rather than a random alias <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</p>
<p>So essentially all my aliases were turned into abbreviations.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">shellAbbrs</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">vim</span> <span class="o">=</span> <span class="s2">&#34;nvim&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">n</span> <span class="o">=</span> <span class="s2">&#34;nvim&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">ss</span> <span class="o">=</span> <span class="s2">&#34;zellij -l welcome&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">cd</span> <span class="o">=</span> <span class="s2">&#34;z&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">cdi</span> <span class="o">=</span> <span class="s2">&#34;zi&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">cp</span> <span class="o">=</span> <span class="s2">&#34;xcp&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">grep</span> <span class="o">=</span> <span class="s2">&#34;rg&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">dig</span> <span class="o">=</span> <span class="s2">&#34;dog&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">cat</span> <span class="o">=</span> <span class="s2">&#34;bat&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">curl</span> <span class="o">=</span> <span class="s2">&#34;curlie&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">rm</span> <span class="o">=</span> <span class="s2">&#34;trash&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">ping</span> <span class="o">=</span> <span class="s2">&#34;gping&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">ls</span> <span class="o">=</span> <span class="s2">&#34;eza&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">sl</span> <span class="o">=</span> <span class="s2">&#34;eza&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">l</span> <span class="o">=</span> <span class="s2">&#34;eza --group --header --group-directories-first --long --git --all --binary --all --icons always&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">tree</span> <span class="o">=</span> <span class="s2">&#34;eza --tree&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># nix</span>
</span></span><span class="line"><span class="cl">        <span class="n">nhh</span> <span class="o">=</span> <span class="s2">&#34;nh home switch&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">nho</span> <span class="o">=</span> <span class="s2">&#34;nh os switch&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">nhu</span> <span class="o">=</span> <span class="s2">&#34;nh os --update&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">nd</span> <span class="o">=</span> <span class="s2">&#34;nix develop&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">nfu</span> <span class="o">=</span> <span class="s2">&#34;nix flake update&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># new commads</span>
</span></span><span class="line"><span class="cl">        <span class="n">weather</span> <span class="o">=</span> <span class="s2">&#34;curl wttr.in/London&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">pfile</span> <span class="o">=</span> <span class="s2">&#34;fzf --preview &#39;bat --style=numbers --color=always --line-range :500 {}&#39;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">gdub</span> <span class="o">=</span> <span class="s2">&#34;git fetch -p &amp;&amp; git branch -vv | grep &#39;: gone]&#39; | awk &#39;{print }&#39; | xargs git branch -D $argv;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">tldrf</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">tldr</span><span class="si">}</span><span class="s2">/bin/tldr --list | fzf --preview </span><span class="se">\&#34;</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">tldr</span><span class="si">}</span><span class="s2">/bin/tldr {1} --color</span><span class="se">\&#34;</span><span class="s2"> --preview-window=right,70% | xargs tldr&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>Some of my most useful abbreviations</p>
<ul>
<li><code>pfile</code>: To find a file using <code>fzf</code> and <code>bat</code></li>
<li><code>gdub</code>: Deletes all local branches which don&rsquo;t exist on remote any more</li>
<li><code>tldrf</code>: Search <code>tldr</code> using <code>fzf</code> to find</li>
<li><code>weather</code>: Displays the weather for London in my terminal</li>
</ul>
<h4 id="tldr">tldr</h4>
<p>For those who don&rsquo;t know nix syntax super well, the <code>pkgs.tldr</code> will eventually be turned into a path in the <code>/nix/store</code>.
So we don&rsquo;t actually need to have <code>tldr</code> installed and available in our PATH variable. If we just want to use it in this
one place we can do the following below:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">tldrf</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">tldr</span><span class="si">}</span><span class="s2">/bin/tldr --list | fzf --preview </span><span class="se">\&#34;</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">tldr</span><span class="si">}</span><span class="s2">/bin/tldr {1} --color</span><span class="se">\&#34;</span><span class="s2"> --preview-window=right,70% | xargs tldr&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>In my <code>~/.config/fish/config.fish</code> it looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fish" data-lang="fish"><span class="line"><span class="cl"><span class="nb">abbr</span> <span class="na">--add</span> <span class="na">-- tldrf</span> <span class="s1">&#39;/nix/store/yj808bpjs1pircgkhxnpqy0gjc61c6fk-tldr-1.6.1/bin/tldr --list | fzf --preview &#34;/nix/store/yj808bpjs1pircgkhxnpqy0gjc61c6fk-tldr-1.6.1/bin/tldr {1} --color&#34; --preview-window=right,70% | xargs tldr&#39;</span>
</span></span></code></pre></div><p>Looking in that directory, we can see the following:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">eza ... /nix/store/yj808bpjs1pircgkhxnpqy0gjc61c6fk-tldr-1.6.1/
</span></span><span class="line"><span class="cl">Permissions Size User Group  Date Modified Name
</span></span><span class="line"><span class="cl">dr-xr-xr-x     - root root    <span class="m">1</span> Jan  <span class="m">1970</span>   .
</span></span><span class="line"><span class="cl">drwxrwxr-t     - root nixbld  <span class="m">1</span> Jul 14:57   ..
</span></span><span class="line"><span class="cl">dr-xr-xr-x     - root root    <span class="m">1</span> Jan  <span class="m">1970</span>   bin
</span></span><span class="line"><span class="cl">dr-xr-xr-x     - root root    <span class="m">1</span> Jan  <span class="m">1970</span>   share
</span></span></code></pre></div><p><img
        loading="lazy"
        src="/posts/2024-07-01-part-6-fish-shell-as-part-of-your-development-workflow/images/weather.png"
        type=""
        alt="weather curl"
        
      /></p>
<h2 id="prompt">Prompt</h2>
<p><a href="https://gitlab.com/hmajid2301/dotfiles/-/blob/d62db156dc89ec6ea3b30f479fa7197c8bcafd4a/modules/home/cli/programs/starship/default.nix">My current Starship Prompt Nix config</a>.</p>
<p>Finally, semi-related to my shell, I also use <a href="https://starship.rs/">starship prompt</a>. I moved to starship prompt at the
same time, I moved to fish shell. So I always associate the two together. It is very flexible, however I haven&rsquo;t
configured it myself.</p>
<p><img
        loading="lazy"
        src="/posts/2024-07-01-part-6-fish-shell-as-part-of-your-development-workflow/images/prompt.png"
        type=""
        alt="My prompt"
        
      /></p>
<h2 id="terminal">Terminal</h2>
<p>Finally, in my terminal emulator I just specify fish as my default shell</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="n">programs</span><span class="o">.</span><span class="n">foot</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">settings</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">main</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">term</span> <span class="o">=</span> <span class="s2">&#34;foot&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">font</span> <span class="o">=</span> <span class="s2">&#34;MonoLisa Nerd Font:size=14&#34;</span><span class="p">;</span>
</span></span><span class="line hl"><span class="cl">      <span class="n">shell</span> <span class="o">=</span> <span class="s2">&#34;fish&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">pad</span> <span class="o">=</span> <span class="s2">&#34;15x15&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">selection-target</span> <span class="o">=</span> <span class="s2">&#34;clipboard&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">scrollback</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">lines</span> <span class="o">=</span> <span class="mi">10000</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"> <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>That&rsquo;s it! That is my shell of choice, why I use it and how I configure it!</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://allanmacgregor.com/posts/using-abbreviations-instead-of-aliases/">https://allanmacgregor.com/posts/using-abbreviations-instead-of-aliases/</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>Rewriting a Python script as a Bash one-liner</title>
      <link>https://haseebmajid.dev/posts/2024-06-30-rewriting-a-python-script-as-a-bash-one-liner/</link>
      <pubDate>Sun, 30 Jun 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-06-30-rewriting-a-python-script-as-a-bash-one-liner/</guid>
      <description>&lt;p&gt;In this post, I will describe how I went about replacing a Python with a bash liner and how it decreased the runtime from 30 seconds to 1 second&lt;/p&gt;
&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;p&gt;For this blog, I used to have a Python script which I would use to generate open graph images. These are images you sometimes see in apps when you share a link. For example:&lt;/p&gt;
&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2024-06-30-rewriting-a-python-script-as-a-bash-one-liner/images/og.png&#34;
        type=&#34;&#34;
        alt=&#34;open graph image&#34;
        
      /&gt;&lt;/p&gt;
&lt;p&gt;Essentially, what it would do is go through every post in my blog repository.
Then run a go program to generate the open graph images based on the markdown
front matter.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this post, I will describe how I went about replacing a Python with a bash liner and how it decreased the runtime from 30 seconds to 1 second</p>
<h2 id="background">Background</h2>
<p>For this blog, I used to have a Python script which I would use to generate open graph images. These are images you sometimes see in apps when you share a link. For example:</p>
<p><img
        loading="lazy"
        src="/posts/2024-06-30-rewriting-a-python-script-as-a-bash-one-liner/images/og.png"
        type=""
        alt="open graph image"
        
      /></p>
<p>Essentially, what it would do is go through every post in my blog repository.
Then run a go program to generate the open graph images based on the markdown
front matter.</p>
<p>Where the structure of my blog looks like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">├── 2024-05-27-how-i-fixed-hibernation-on-my-nixos-machine
</span></span><span class="line"><span class="cl">│  ├── images
</span></span><span class="line"><span class="cl">│  │  └── cover.png
</span></span><span class="line"><span class="cl">│  └── index.md
</span></span><span class="line"><span class="cl">├── 2024-06-02-til-how-to-fix-dns-resolution-issues-in-k3s-related-to-flannel-networking
</span></span><span class="line"><span class="cl">│  ├── images
</span></span><span class="line"><span class="cl">│  │  └── cover.png
</span></span><span class="line"><span class="cl">│  └── index.md
</span></span><span class="line"><span class="cl">├── 2024-06-15-til-how-to-fix-did-no-resolve-alias-errors-in-podman
</span></span><span class="line"><span class="cl">│  ├── images
</span></span><span class="line"><span class="cl">│  │  └── cover.png
</span></span><span class="line"><span class="cl">│  └── index.md
</span></span><span class="line"><span class="cl">├── 2024-06-17-migrating-my-homelab-to-flux
</span></span><span class="line"><span class="cl">│  ├── images
</span></span><span class="line"><span class="cl">│  │  └── cover.png
</span></span><span class="line"><span class="cl">│  └── index.md
</span></span><span class="line"><span class="cl">└── 2024-06-30-rewriting-a-python-script-as-a-bash-one-liner
</span></span><span class="line"><span class="cl">   ├── images
</span></span><span class="line"><span class="cl">   │  └── cover.png
</span></span><span class="line"><span class="cl">   └── index.md
</span></span></code></pre></div><p>Each post gets its own folder, where the image generated is called cover.png.
It uses the front matter in the index.md to generate the open graph image.</p>
<h2 id="how">How?</h2>
<h3 id="python">python</h3>
<p>To generate the open graph images, I forked this go &ldquo;script&rdquo; called tcardgen you can find my version
<a href="https://github.com/hmajid2301/tcardgen">here</a> <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>. Where the python script looked like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">subprocess</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">create_og_image</span><span class="p">(</span><span class="n">full_path</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isdir</span><span class="p">(</span><span class="n">full_path</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">Path</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">full_path</span><span class="si">}</span><span class="s2">/images&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">mkdir</span><span class="p">(</span><span class="n">parents</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">exist_ok</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">generate_og</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;tcardgen -c scripts/og/config.yaml --template=scripts/og/template.png -f scripts/og/fonts/ </span><span class="si">{</span><span class="n">full_path</span><span class="si">}</span><span class="s2">/index.md -o </span><span class="si">{</span><span class="n">full_path</span><span class="si">}</span><span class="s2">/images/cover.png&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="n">full_path</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="n">subprocess</span><span class="o">.</span><span class="n">Popen</span><span class="p">(</span><span class="n">generate_og</span><span class="p">,</span> <span class="n">shell</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">stdout</span><span class="o">=</span><span class="n">subprocess</span><span class="o">.</span><span class="n">PIPE</span><span class="p">)</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">read</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">==</span> <span class="s2">&#34;all&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">content_path</span> <span class="o">=</span> <span class="s1">&#39;content/posts/&#39;</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">path</span> <span class="ow">in</span> <span class="n">os</span><span class="o">.</span><span class="n">listdir</span><span class="p">(</span><span class="n">content_path</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">full_path</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">content_path</span><span class="p">,</span> <span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">create_og_image</span><span class="p">(</span><span class="n">full_path</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">full_path</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">create_og_image</span><span class="p">(</span><span class="n">full_path</span><span class="p">)</span>
</span></span></code></pre></div><p><a href="https://explainshell.com/explain?cmd=ls+content%2Fposts%2F+%7C+parallel+%27mkdir+-p+content%2Fposts%2F%7B%7D%2Fimages+%26%26+tcardgen+-c+scripts%2Fog%2Fconfig.yaml+--template%3Dscripts%2Fog%2Ftemplate.png+-f+scripts%2Fog%2Ffonts%2F+content%2Fposts%2F%7B%7D%2Findex.md+-o+content%2Fposts%2F%7B%7D%2Fimages%2Fcover.png%27">https://explainshell.com/explain?cmd=ls+content%2Fposts%2F+%7C+parallel+%27mkdir+-p+content%2Fposts%2F%7B%7D%2Fimages+%26%26+tcardgen+-c+scripts%2Fog%2Fconfig.yaml+--template%3Dscripts%2Fog%2Ftemplate.png+-f+scripts%2Fog%2Ffonts%2F+content%2Fposts%2F%7B%7D%2Findex.md+-o+content%2Fposts%2F%7B%7D%2Fimages%2Fcover.png%27</a>
Again, just going through each folder in content/posts/, where each folder is its own blog post.
It was taking about 30 seconds to run on for every blog post, i.e. <code>python ./scripts/og/generate &quot;all&quot;</code>.</p>
<h3 id="bash">bash</h3>
<p>The bash script looked like <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ls content/posts/ <span class="p">|</span> parallel <span class="s1">&#39;mkdir -p content/posts/{}/images &amp;&amp; tcardgen -c scripts/og/config.yaml --template=scripts/og/template.png -f scripts/og/fonts/ content/posts/{}/index.md -o content/posts/{}/images/cover.png&#39;</span>
</span></span></code></pre></div><p>Breaking this down:</p>
<ul>
<li><code>ls content/posts/</code>: Lists all blog posts in a folder.</li>
<li><code>| parallel</code>: We pipe this output to the parallel command which will then run an arbitrary command we pass it, in parallel, which each of the inputs. Where each line from the <code>ls</code> is a separate input (separate blog post).</li>
<li><code>mkdir -p content/posts/{}/images</code>: Creates an images&rsquo; folder if it doesn&rsquo;t exist. Where <code>{}</code> is the folder name taken from the input, i.e. the ls content/posts</li>
<li><code>tcardgen ...</code>: Is the specific used to generate open graph image from our index.md front matter. Again, it uses <code>{}</code> as the folder name.</li>
</ul>
<details
  class="notice warn"
  open="true"
>
    <summary class="notice-title">Bash</summary>
  
  I am by no means a bash expert, and it is likely the script above can be improved. But it is good enough for my use case at the moment.
</details>

<h2 id="why-move-to-bash">Why move to bash?</h2>
<p>In theory, I would only need to run this once when I first create a blog post. However, sometimes you end up changing
the title or the date, and then need to generate the image again. And to be honest, I was too lazy to run it in a specific
folder and would just let it run on everything.</p>
<p>This had the added benefit it would also fix any images that I forgot to update. Now 30 seconds isn&rsquo;t long, but I
thought it&rsquo;s basically just looping through a folder, just to do fork a child process to run a go binary.
This will probably be faster in Bash. Also had been watching the <a href="https://github.com/ThePrimeagen">Primeagen</a> who had been talking about learning
your tools better. So I thought adding parallel as well would just speed it up.</p>
<p>Now there is less code in the project, one fewer script to maintain. Anyone else forking this repository won&rsquo;t need python just to run a simple script as well.</p>
<p>That&rsquo;s it! A rather boring article about doing a small improvement, by moving a script from Python to a bash one-liner.</p>
<h2 id="further">Further</h2>
<p>Some things I would like to add to my blog repository include:</p>
<ul>
<li>Add this to CI, where if any file&rsquo;s change fails CI, i.e. open graph images aren&rsquo;t up-to-date</li>
<li>Add it as a pre-commit hook but just for files that changed</li>
</ul>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://github.com/Ladicle/tcardgen">Original</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://explainshell.com/explain?cmd=ls+content%2Fposts%2F+%7C+parallel+%27mkdir+-p+content%2Fposts%2F%7B%7D%2Fimages+%26%26+tcardgen+-c+scripts%2Fog%2Fconfig.yaml+--template%3Dscripts%2Fog%2Ftemplate.png+-f+scripts%2Fog%2Ffonts%2F+content%2Fposts%2F%7B%7D%2Findex.md+-o+content%2Fposts%2F%7B%7D%2Fimages%2Fcover.png%27">ExplainShell for the bash command</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>Migrating My Homelab to Flux</title>
      <link>https://haseebmajid.dev/posts/2024-06-17-migrating-my-homelab-to-flux/</link>
      <pubDate>Mon, 17 Jun 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-06-17-migrating-my-homelab-to-flux/</guid>
      <description>&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;p&gt;This series is a continuation of the other &lt;a href=&#34;https://haseebmajid.dev/series/setup-raspberry-pi-cluster-with-k3s-and-nixos/&#34;&gt;series&lt;/a&gt;. I have
since updated my &lt;a href=&#34;https://docs.homelab.haseebmajid.dev/&#34;&gt;home lab&lt;/a&gt;, removing the RPIs and replacing them with some
mini pcs.&lt;/p&gt;
&lt;p&gt;As part of this change I am now using &lt;a href=&#34;https://github.com/serokell/deploy-rs&#34;&gt;deploy-rs&lt;/a&gt; instead of colmena. As its
easier to integrate into my own flake, and it won&amp;rsquo;t roll out the change if breaks the networking, i.e. you cannot ssh
to the machine.&lt;/p&gt;
&lt;h2 id=&#34;why-move-away-from-pulumi&#34;&gt;Why move away from Pulumi?&lt;/h2&gt;
&lt;p&gt;As per the title of this post of the most significant changes I have made it moving my Kubernetes config from Pulumi to fluxcd.
Pulumi I suspect is great for deploying infrastructure but became painful for managing the YAML config for the k3s
cluster.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="background">Background</h2>
<p>This series is a continuation of the other <a href="/series/setup-raspberry-pi-cluster-with-k3s-and-nixos/">series</a>. I have
since updated my <a href="https://docs.homelab.haseebmajid.dev/">home lab</a>, removing the RPIs and replacing them with some
mini pcs.</p>
<p>As part of this change I am now using <a href="https://github.com/serokell/deploy-rs">deploy-rs</a> instead of colmena. As its
easier to integrate into my own flake, and it won&rsquo;t roll out the change if breaks the networking, i.e. you cannot ssh
to the machine.</p>
<h2 id="why-move-away-from-pulumi">Why move away from Pulumi?</h2>
<p>As per the title of this post of the most significant changes I have made it moving my Kubernetes config from Pulumi to fluxcd.
Pulumi I suspect is great for deploying infrastructure but became painful for managing the YAML config for the k3s
cluster.</p>
<p>Writing the YAML in go was an abstraction on top of YAML, making things more complicated. The main thing that caused
me to move was trying to set up cert manager. I kept having issues, whereas it was a lot less painful to do in fluxcd.</p>
<p>As I said, I may still use Pulumi to deploy infrastructure changes such as creating DNS records for applications. But
I reckon stick to YAML for Kubernetes config.</p>
<p>It is also a lot easier to find tutorials as most Kubernetes resources are in YAML, even though you can convert from
YAML to Pulumi Go. If it is still extra work you need to do. Even when I used copilot to try to do it for me.</p>
<h2 id="what-is-flux">What is flux?</h2>
<p><a href="https://fluxcd.io/">fluxcd</a> is a tool which keeps your Kubernetes cluster in sync with say a git repository. I have
used it at work previously, and it falls into the category of tools of Git Ops. Rather than pushing your changes to the
cluster. Flux polls the git repository for changes every x minutes and applies those changes for you.</p>
<p>That way you cannot really apply, and any changes manually forget to commit them, as these will be overwritten by flux.
I believe, I am not an expert in fluxcd I am very much still learning both flux and Kubernetes. So please do take what you
read here with a pinch of salt.</p>
<h2 id="setup">Setup</h2>
<p>Install fluxcd, if you are nix, we can do something like this below, or we can add it to say a devshell.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">nix-shell -p fluxcd
</span></span></code></pre></div><p>You must have a kube config file setup such that you can connect to you cluster i.e. <code>kubectl get nodes</code>  returns
the nodes of your cluster. For my k3s cluster I have it in <code>~/.kube/config.personal</code> and then an environment variable
in the devshell of my home lab.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">description</span> <span class="o">=</span> <span class="s2">&#34;Developer Shell&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">inputs</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">nixpkgs</span><span class="o">.</span><span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;github:nixos/nixpkgs/nixos-unstable&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">outputs</span> <span class="o">=</span> <span class="p">{</span><span class="n">nixpkgs</span><span class="o">,</span> <span class="o">...</span><span class="p">}:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">devShell</span><span class="o">.</span><span class="n">x86_64-linux</span> <span class="o">=</span> <span class="k">let</span>
</span></span><span class="line"><span class="cl">      <span class="n">pkgs</span> <span class="o">=</span> <span class="n">nixpkgs</span><span class="o">.</span><span class="n">legacyPackages</span><span class="o">.</span><span class="n">x86_64-linux</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">in</span>
</span></span><span class="line"><span class="cl">      <span class="n">pkgs</span><span class="o">.</span><span class="n">mkShell</span> <span class="p">{</span>
</span></span><span class="line hl"><span class="cl">        <span class="n">shellHook</span> <span class="o">=</span> <span class="s1">&#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">          export KUBECONFIG=~/.kube/config.personal
</span></span></span><span class="line"><span class="cl"><span class="s1">        &#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">packages</span> <span class="o">=</span> <span class="k">with</span> <span class="n">pkgs</span><span class="p">;</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">          <span class="n">fluxcd</span>
</span></span><span class="line"><span class="cl">          <span class="n">kubectl</span>
</span></span><span class="line"><span class="cl">        <span class="p">];</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Then we can run install flux by doing something like so (set a valid GitLab token).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">GITLAB_TOKEN</span><span class="o">=</span>deploy-token <span class="c1"># Change this to your token</span>
</span></span><span class="line"><span class="cl">flux bootstrap gitlab <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>        --owner<span class="o">=</span>hmajid2301 <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>        --repository<span class="o">=</span>home-lab <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>        --branch<span class="o">=</span>main <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>        --path<span class="o">=</span>clusters/ <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>        --personal --deploy-token-auth
</span></span></code></pre></div><p>That&rsquo;s it, now we can add config to the <code>home-lab</code> repository. For example, to expose the traefik dashboard we could
create a new file <code>clusters/traefik/dashboard-service.yaml</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">Service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">traefik-dashboard</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">kube-system</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">app.kubernetes.io/instance</span><span class="p">:</span><span class="w"> </span><span class="l">traefik</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">app.kubernetes.io/name</span><span class="p">:</span><span class="w"> </span><span class="l">traefik-dashboard</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">ClusterIP</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">traefik</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="m">9000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">targetPort</span><span class="p">:</span><span class="w"> </span><span class="l">traefik</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">protocol</span><span class="p">:</span><span class="w"> </span><span class="l">TCP</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">selector</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">app.kubernetes.io/instance</span><span class="p">:</span><span class="w"> </span><span class="l">traefik-kube-system</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">app.kubernetes.io/name</span><span class="p">:</span><span class="w"> </span><span class="l">traefik</span><span class="w">
</span></span></span></code></pre></div><p>Then commit and push our change to our <code>main</code> branch. Flux will pick up the change and eventually apply it to our cluster.
We can monitor the changes using:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">flux logs
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># or</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">flux events
</span></span></code></pre></div><p>We can then access the dashboard by doing some port forwarding <code>kubectl --namespace kube-system port-forward deployments/traefik 9000:9000</code>
Then go to <code>localhost:9000/dashboard/</code>.</p>
<p>That&rsquo;s it! We quickly set up fluxcd in our Kubernetes cluster. We can now add update the config in our git repo. In
the next post we will cover how we can set up sops to secure our secrets but keep them in git.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Fix Did No Resolve Alias Errors in Podman</title>
      <link>https://haseebmajid.dev/posts/2024-06-15-til-how-to-fix-did-no-resolve-alias-errors-in-podman/</link>
      <pubDate>Sat, 15 Jun 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-06-15-til-how-to-fix-did-no-resolve-alias-errors-in-podman/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Fix Did No Resolve Alias Errors in Podman&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Recently, I was trying to pull docker images using &lt;code&gt;podman&lt;/code&gt;, on an Ubuntu laptop and was getting an error which
looked something like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Error: error creating build container: short-name &lt;span class=&#34;s2&#34;&gt;&amp;#34;node:18.17&amp;#34;&lt;/span&gt; did not resolve to an &lt;span class=&#34;nb&#34;&gt;alias&lt;/span&gt; and no unqualified-search registries are defined in &lt;span class=&#34;s2&#34;&gt;&amp;#34;/etc/containers/registries.conf&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is because Podman doesn&amp;rsquo;t allow us to use short names, by default we need to specify the registry i.e. &lt;code&gt;docker.io/node:18.17&lt;/code&gt;.
But for existing &lt;code&gt;docker-compose.yml&lt;/code&gt; files, this would be a pain to edit. Especially because most people use Docker
not Podman. So, in the end, you can edit a config file in home directory to enable the old way of pulling images.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Fix Did No Resolve Alias Errors in Podman</strong></p>
<p>Recently, I was trying to pull docker images using <code>podman</code>, on an Ubuntu laptop and was getting an error which
looked something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">Error: error creating build container: short-name <span class="s2">&#34;node:18.17&#34;</span> did not resolve to an <span class="nb">alias</span> and no unqualified-search registries are defined in <span class="s2">&#34;/etc/containers/registries.conf&#34;</span>
</span></span></code></pre></div><p>This is because Podman doesn&rsquo;t allow us to use short names, by default we need to specify the registry i.e. <code>docker.io/node:18.17</code>.
But for existing <code>docker-compose.yml</code> files, this would be a pain to edit. Especially because most people use Docker
not Podman. So, in the end, you can edit a config file in home directory to enable the old way of pulling images.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">nvim ~/.config/containers/registries.conf
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Add the following</span>
</span></span><span class="line"><span class="cl">unqualified-search-registries<span class="o">=[</span><span class="s2">&#34;docker.io&#34;</span><span class="o">]</span>
</span></span></code></pre></div><p>You could also do it machine wide by editing the file <code>/etc/containers/registries.conf</code>.</p>
<p>That&rsquo;s it! Real short one today! This just stumped me today for like 20 minutes.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Fix DNS Resolution Issues in K3s Related to Flannel Networking</title>
      <link>https://haseebmajid.dev/posts/2024-06-02-til-how-to-fix-dns-resolution-issues-in-k3s-related-to-flannel-networking/</link>
      <pubDate>Sun, 02 Jun 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-06-02-til-how-to-fix-dns-resolution-issues-in-k3s-related-to-flannel-networking/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Fix DNS Resolution Issues in K3s Related to Flannel Networking&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Recently, I was trying to set up the kubernetes-dashboard, to make it easier to monitor my k8s cluster. I however noticed
I was getting the following error:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;… &amp;gt; add-kubernetes-dashboard via 🐹 v1.22.2 via   via ❄  impure &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;nix-shell-env&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; k$ kubectl logs -n monitoring kubernetes-dashboard-kong-75bb76dd5f-b27ll
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2024/05/05 20:55:21 &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;error&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt; 1319#0: *274054 &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;lua&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt; init.lua:371: execute&lt;span class=&#34;o&#34;&gt;()&lt;/span&gt;: DNS resolution failed: failed to receive reply from UDP server 10.43.0.10:53: timeout. Tried: nil, client: 127.0.0.1, server: kong, request: &lt;span class=&#34;s2&#34;&gt;&amp;#34;GET / HTTP/2.0&amp;#34;&lt;/span&gt;, host: &lt;span class=&#34;s2&#34;&gt;&amp;#34;localhost:8443&amp;#34;&lt;/span&gt;, request_id: &lt;span class=&#34;s2&#34;&gt;&amp;#34;af30b3162db70e2de8f1073a40f7d865&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Weird the DNS resolution seems to be failing, so I decided to follow the
&lt;a href=&#34;https://kubernetes.io/docs/tasks/administer-cluster/dns-debugging-resolution/&#34;&gt;Kubernetes guide&lt;/a&gt; to try to debug issues.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Fix DNS Resolution Issues in K3s Related to Flannel Networking</strong></p>
<p>Recently, I was trying to set up the kubernetes-dashboard, to make it easier to monitor my k8s cluster. I however noticed
I was getting the following error:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">… &gt; add-kubernetes-dashboard via 🐹 v1.22.2 via   via ❄  impure <span class="o">(</span>nix-shell-env<span class="o">)</span>
</span></span><span class="line"><span class="cl"> k$ kubectl logs -n monitoring kubernetes-dashboard-kong-75bb76dd5f-b27ll
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">2024/05/05 20:55:21 <span class="o">[</span>error<span class="o">]</span> 1319#0: *274054 <span class="o">[</span>lua<span class="o">]</span> init.lua:371: execute<span class="o">()</span>: DNS resolution failed: failed to receive reply from UDP server 10.43.0.10:53: timeout. Tried: nil, client: 127.0.0.1, server: kong, request: <span class="s2">&#34;GET / HTTP/2.0&#34;</span>, host: <span class="s2">&#34;localhost:8443&#34;</span>, request_id: <span class="s2">&#34;af30b3162db70e2de8f1073a40f7d865&#34;</span>
</span></span></code></pre></div><p>Weird the DNS resolution seems to be failing, so I decided to follow the
<a href="https://kubernetes.io/docs/tasks/administer-cluster/dns-debugging-resolution/">Kubernetes guide</a> to try to debug issues.</p>
<p>I found out I couldn&rsquo;t even do a nslookup:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl <span class="nb">exec</span> -i -t dnsutils -- nslookup kubernetes.default
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">;;</span> connection timed out<span class="p">;</span> no servers could be reached
</span></span><span class="line"><span class="cl"><span class="nb">command</span> terminated with <span class="nb">exit</span> code <span class="m">1</span>
</span></span></code></pre></div><p>I had set up k3s on NixOS (as per the previous posts in this series). In the ended, the issue ending up being not
open the correct ports, we are using the <code>Flannel VXLAN</code> <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>. <code>UDP	8472	All nodes	All nodes	Required only for Flannel VXLAN</code>
The <a href="https://github.com/NixOS/nixpkgs/issues/175513#issuecomment-1147755254">GitHub</a> issue that helped me solve my problem.</p>
<p>We need to open up port 8472 in our firewall rules, on my NixOS server we can do something like so.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="n">networking</span><span class="o">.</span><span class="n">firewall</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">allowedUDPPorts</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">    <span class="mi">8472</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><details
  class="notice danger"
  open="true"
>
    <summary class="notice-title">Security Issues</summary>
  
  The VXLAN port on nodes should not be exposed to the world as it opens up your cluster network to be accessed by anyone. Run your nodes behind a firewall/security group that disables access to port 8472.
</details>

<p>So in my case, since my k8s cluster was multi-node, the nodes couldn&rsquo;t communicate with each other properly.
That&rsquo;s about it, it took me a while to figure out, basically RTFM 😭😭😭.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://docs.k3s.io/installation/requirements#networking">https://docs.k3s.io/installation/requirements#networking</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>How I Fixed Hibernate on My NixOS Machine</title>
      <link>https://haseebmajid.dev/posts/2024-05-27-how-i-fixed-hibernation-on-my-nixos-machine/</link>
      <pubDate>Mon, 27 May 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-05-27-how-i-fixed-hibernation-on-my-nixos-machine/</guid>
      <description>&lt;details
  class=&#34;notice info&#34;
  open=&#34;true&#34;
&gt;
    &lt;summary class=&#34;notice-title&#34;&gt;tl:dr;&lt;/summary&gt;
  
  &lt;p&gt;Wi-Fi drivers were stopping the PC from suspending. I am using an Ethernet cable to connect my PC. So didn&amp;rsquo;t need the
Wi-Fi drivers. By adding them to a blocklist. I think you only need the 2nd one in the list.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;boot&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;blacklistedKernelModules&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;s2&#34;&gt;&amp;#34;ath12k_pci&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;s2&#34;&gt;&amp;#34;ath12k&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/details&gt;

&lt;p&gt;Recently, I upgraded my PC to an am5 machine with an X670E Gigabyte motherboard. However, when I did this hibernate
was left broken, alongside suspend. This gave me an idea, it wasn&amp;rsquo;t to do with my hibernate setup being broken by something
else going on.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">tl:dr;</summary>
  
  <p>Wi-Fi drivers were stopping the PC from suspending. I am using an Ethernet cable to connect my PC. So didn&rsquo;t need the
Wi-Fi drivers. By adding them to a blocklist. I think you only need the 2nd one in the list.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">boot</span><span class="o">.</span><span class="n">blacklistedKernelModules</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;ath12k_pci&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;ath12k&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div>
</details>

<p>Recently, I upgraded my PC to an am5 machine with an X670E Gigabyte motherboard. However, when I did this hibernate
was left broken, alongside suspend. This gave me an idea, it wasn&rsquo;t to do with my hibernate setup being broken by something
else going on.</p>
<p>So I started debugging, looking at the systemd logs which we can use <code>journalctl</code> service. Specifically, the <code>journal -f</code>
command to tail all the logs. Then in another terminal I ran <code>systemctl hibernate</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">Apr <span class="m">22</span> 06:06:53 workstation kernel: ath12k_pci 0000:0f:00.0: failed to <span class="nb">suspend</span> core: -95
</span></span><span class="line"><span class="cl"> <span class="m">22</span> 06:06:53 workstation kernel: ath12k_pci 0000:0f:00.0: PM: pci_pm_freeze<span class="o">()</span>: ath12k_pci_pm_suspend+0x0/0x50 <span class="o">[</span>ath12k<span class="o">]</span> returns -95
</span></span><span class="line"><span class="cl"> <span class="m">22</span> 06:06:53 workstation kernel: ath12k_pci 0000:0f:00.0: PM: dpm_run_callback<span class="o">()</span>: pci_pm_freeze+0x0/0xc0 returns -95
</span></span><span class="line"><span class="cl"> <span class="m">22</span> 06:06:53 workstation kernel: ath12k_pci 0000:0f:00.0: PM: failed to freeze async: error -95
</span></span><span class="line"><span class="cl"> <span class="m">22</span> 06:06:53 workstation kernel: nvme nvme1: Shutdown timeout <span class="nb">set</span> to <span class="m">10</span> seconds
</span></span><span class="line"><span class="cl"> <span class="m">22</span> 06:06:53 workstation kernel: nvme nvme1: 8/0/0 default/read/poll queues
</span></span><span class="line"><span class="cl"> <span class="m">22</span> 06:06:53 workstation kernel: nvme nvme1: Ignoring bogus Namespace Identifiers
</span></span><span class="line"><span class="cl"> <span class="m">22</span> 06:06:53 workstation kernel: PM: hibernation: Basic memory bitmaps freed
</span></span><span class="line"><span class="cl"> <span class="m">22</span> 06:06:53 workstation kernel: OOM killer enabled.
</span></span><span class="line"><span class="cl"> <span class="m">22</span> 06:06:53 workstation kernel: Restarting tasks ... <span class="k">done</span>.
</span></span><span class="line"><span class="cl"> <span class="m">22</span> 06:06:53 workstation kernel: PM: hibernation: hibernation <span class="nb">exit</span>
</span></span><span class="line"><span class="cl"> <span class="m">22</span> 06:06:53 workstation systemd-sleep<span class="o">[</span>11861<span class="o">]</span>: Failed to put system to sleep. System resumed again: Operation not supported
</span></span><span class="line"><span class="cl"> <span class="m">22</span> 06:06:53 workstation systemd<span class="o">[</span>1<span class="o">]</span>: systemd-hibernate.service: Main process exited, <span class="nv">code</span><span class="o">=</span>exited, <span class="nv">status</span><span class="o">=</span>1/FAILURE
</span></span><span class="line"><span class="cl"> <span class="m">22</span> 06:06:53 workstation systemd<span class="o">[</span>1<span class="o">]</span>: systemd-hibernate.service: Failed with result <span class="s1">&#39;exit-code&#39;</span>.
</span></span><span class="line"><span class="cl"> <span class="m">22</span> 06:06:53 workstation systemd<span class="o">[</span>1<span class="o">]</span>: Failed to start System Hibernate.
</span></span><span class="line"><span class="cl"> <span class="m">22</span> 06:06:53 workstation systemd<span class="o">[</span>1<span class="o">]</span>: Dependency failed <span class="k">for</span> System Hibernation.
</span></span><span class="line"><span class="cl"> <span class="m">22</span> 06:06:53 workstation systemd<span class="o">[</span>1<span class="o">]</span>: hibernate.target: Job hibernate.target/start failed with result <span class="s1">&#39;dependency&#39;</span>.
</span></span></code></pre></div><p>These logs provide us with a bunch of useful information, we can see that the hibernate job failed.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"> <span class="m">22</span> 06:06:53 workstation kernel: PM: hibernation: Basic memory bitmaps freed
</span></span><span class="line"><span class="cl"> <span class="m">22</span> 06:06:53 workstation kernel: OOM killer enabled.
</span></span><span class="line"><span class="cl"> <span class="m">22</span> 06:06:53 workstation kernel: Restarting tasks ... <span class="k">done</span>.
</span></span><span class="line"><span class="cl"> <span class="m">22</span> 06:06:53 workstation kernel: PM: hibernation: hibernation <span class="nb">exit</span>
</span></span><span class="line"><span class="cl"> <span class="m">22</span> 06:06:53 workstation systemd-sleep<span class="o">[</span>11861<span class="o">]</span>: Failed to put system to sleep. System resumed again: Operation not supported
</span></span><span class="line"><span class="cl"> <span class="m">22</span> 06:06:53 workstation systemd<span class="o">[</span>1<span class="o">]</span>: systemd-hibernate.service: Main process exited, <span class="nv">code</span><span class="o">=</span>exited, <span class="nv">status</span><span class="o">=</span>1/FAILURE
</span></span><span class="line"><span class="cl"> <span class="m">22</span> 06:06:53 workstation systemd<span class="o">[</span>1<span class="o">]</span>: systemd-hibernate.service: Failed with result <span class="s1">&#39;exit-code&#39;</span>.
</span></span><span class="line"><span class="cl"> <span class="m">22</span> 06:06:53 workstation systemd<span class="o">[</span>1<span class="o">]</span>: Failed to start System Hibernate.
</span></span><span class="line"><span class="cl"> <span class="m">22</span> 06:06:53 workstation systemd<span class="o">[</span>1<span class="o">]</span>: Dependency failed <span class="k">for</span> System Hibernation.
</span></span><span class="line"><span class="cl"> <span class="m">22</span> 06:06:53 workstation systemd<span class="o">[</span>1<span class="o">]</span>: hibernate.target: Job hibernate.target/start failed with result <span class="s1">&#39;dependency&#39;</span>.
</span></span></code></pre></div><p>However, I could not find an exact reason within the job itself. After trying numerous random things, I looked closer
at the logs above and noticed the mention of the Wi-Fi drivers <code>ath12k_pci</code> failing to suspend.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">Apr <span class="m">22</span> 06:06:53 workstation kernel: ath12k_pci 0000:0f:00.0: failed to <span class="nb">suspend</span> core: -95
</span></span><span class="line"><span class="cl"> <span class="m">22</span> 06:06:53 workstation kernel: ath12k_pci 0000:0f:00.0: PM: pci_pm_freeze<span class="o">()</span>: ath12k_pci_pm_suspend+0x0/0x50 <span class="o">[</span>ath12k<span class="o">]</span> returns -95
</span></span><span class="line"><span class="cl"> <span class="m">22</span> 06:06:53 workstation kernel: ath12k_pci 0000:0f:00.0: PM: dpm_run_callback<span class="o">()</span>: pci_pm_freeze+0x0/0xc0 returns -95
</span></span><span class="line"><span class="cl"> <span class="m">22</span> 06:06:53 workstation kernel: ath12k_pci 0000:0f:00.0: PM: failed to freeze async: error -95
</span></span></code></pre></div><p>This then gave me the idea to try to add the drivers to the blocklist and see if that resolves the issue. Like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">boot</span><span class="o">.</span><span class="n">blacklistedKernelModules</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;ath12k_pci&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;ath12k&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>That&rsquo;s it! Hibernate and suspend are now working. Of course, the downside now for my desktop I cannot use the Wi-Fi
built into my motherboard. But that&rsquo;s an issue for another day, currently I have it connected via an Ethernet cable.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Add reveal-hugo to a Hugo Site</title>
      <link>https://haseebmajid.dev/posts/2024-05-26-how-to-add-hugo-revealjs-to-a-hugo-site/</link>
      <pubDate>Sun, 26 May 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-05-26-how-to-add-hugo-revealjs-to-a-hugo-site/</guid>
      <description>&lt;p&gt;What we are trying to achieve hosting RevealJS slides on our Hugo blog like
&lt;a href=&#34;https://haseebmajid.dev/slides/reproducible-envs-with-nix/#/&#34;&gt;so&lt;/a&gt;. The markdown that the slides are built from
It can be found &lt;a href=&#34;https://gitlab.com/hmajid2301/blog/-/blob/main/content/slides/reproducible-envs-with-nix/_index.md&#34;&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;p&gt;Recently, I did a talk at the Conf42 conference &lt;a href=&#34;https://haseebmajid.dev/content/talks/reproducible-envs-with-nix&#34;&gt;shameless plug here&lt;/a&gt;. At
the time I was working on the slides using &lt;a href=&#34;https://revealjs.com/&#34;&gt;Reveal.js&lt;/a&gt;, as I did for all of my slides
as I can create a slideshow using just plain markdown. I hosted all of my talks in a separate
&lt;a href=&#34;https://gitlab.com/hmajid2301/talks&#34;&gt;repository&lt;/a&gt;. Which published to a simple &lt;a href=&#34;https://talks.haseebmajid.dev/&#34;&gt;site&lt;/a&gt;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>What we are trying to achieve hosting RevealJS slides on our Hugo blog like
<a href="https://haseebmajid.dev/slides/reproducible-envs-with-nix/#/">so</a>. The markdown that the slides are built from
It can be found <a href="https://gitlab.com/hmajid2301/blog/-/blob/main/content/slides/reproducible-envs-with-nix/_index.md">here</a></p>
<h2 id="background">Background</h2>
<p>Recently, I did a talk at the Conf42 conference <a href="/content/talks/reproducible-envs-with-nix">shameless plug here</a>. At
the time I was working on the slides using <a href="https://revealjs.com/">Reveal.js</a>, as I did for all of my slides
as I can create a slideshow using just plain markdown. I hosted all of my talks in a separate
<a href="https://gitlab.com/hmajid2301/talks">repository</a>. Which published to a simple <a href="https://talks.haseebmajid.dev/">site</a>.</p>
<p>However, I wanted to move all the talks into a blog so I could manage all in one place. As I already had a section
for my talks, which then linked to this other site. I then came across this
<a href="https://github.com/joshed-io/reveal-hugo">project</a>.</p>
<h2 id="getting-started">Getting Started</h2>
<p>Let&rsquo;s assume you already have a Hugo site, and we will add reveal-hugo to it. If not, you can follow this simple
<a href="https://gohugo.io/getting-started/quick-start/#explanation-of-commands">getting started guide</a>. To set up a basic
Hugo site.</p>
<h3 id="issues">Issues</h3>
<p>So one problem I found with adding reveal-hugo was to get code highlighting using highlight-js, we need to turn off
code fences on our Hugo site. Which means we need to use the Hugo short code to add code blocks to our blog posts.
Which means instead of doing:</p>
<pre tabindex="0"><code>```md
</code></pre><p>We need to do:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx"><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">199
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// ... code
</span></span></span></code></pre></td></tr></table>
</div>
</div></span>
</span></span></code></pre></div><p>But we have to specify the style everytime as well (from what I recall). So in the end I decided just to have two Hugo
sites in the same repository. My main one which would be hosted at <code>haseebmajid.dev</code> then my slides one which which use
<code>haseebmajid.dev/slides/</code>. During the build process we would run <code>hugo &amp;&amp; hugo --config hugo-slides.toml</code>. Then our
site is available in <code>public</code> folder.</p>
<p>Let me know if you know a better way to solve this problem, but it works well enough for now if a bit &ldquo;hacky&rdquo;.</p>
<h2 id="add-reveal-hugo">Add reveal-hugo</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">hugo mod init example
</span></span><span class="line"><span class="cl">hugo mod get github.com/dzello/reveal-hugo
</span></span></code></pre></div><p>Create another hugo file for the slides.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">nvim hugo-slides.toml
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="nx">baseURL</span> <span class="p">=</span> <span class="s2">&#34;https://example.com/slides&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">title</span> <span class="p">=</span> <span class="s2">&#34;Example Blog&#34;</span>
</span></span><span class="line hl"><span class="cl"><span class="nx">theme</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;github.com/dzello/reveal-hugo&#34;</span><span class="p">]</span>
</span></span><span class="line hl"><span class="cl"><span class="nx">contentDir</span> <span class="p">=</span> <span class="s2">&#34;content/slides&#34;</span>
</span></span><span class="line hl"><span class="cl"><span class="nx">publishDir</span> <span class="p">=</span> <span class="s2">&#34;public/slides&#34;</span>
</span></span><span class="line hl"><span class="cl"><span class="nx">staticDir</span> <span class="p">=</span> <span class="s2">&#34;static/slides&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">markup</span><span class="p">.</span><span class="nx">highlight</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">codeFences</span> <span class="p">=</span> <span class="kc">false</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">markup</span><span class="p">.</span><span class="nx">goldmark</span><span class="p">.</span><span class="nx">renderer</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">unsafe</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">outputFormats</span><span class="p">.</span><span class="nx">Reveal</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">baseName</span> <span class="p">=</span> <span class="s2">&#34;index&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">mediaType</span> <span class="p">=</span> <span class="s2">&#34;text/html&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">isHTML</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">sitemap</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">changefreq</span> <span class="p">=</span> <span class="s2">&#34;monthly&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">filename</span> <span class="p">=</span> <span class="s2">&#34;sitemap.xml&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">priority</span> <span class="p">=</span> <span class="mf">0.5</span>
</span></span></code></pre></div><p>The key points here being we are looking at:</p>
<p>First we add the reveal-hugo module as a theme.</p>
<p>Then contentDir, where it will look for our content i.e. markdown for our slides.</p>
<p>Then publishDir where the site will be built when we run <code>hugo --config hugo-slides.toml</code>. Cannot be <code>publish</code> because
it will overwrite our main site.</p>
<p>Finally staticDir, where the slides will look for static content like CSS.</p>
<p>Within our <code>hugo.toml</code> file add the following:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="nx">baseURL</span> <span class="p">=</span> <span class="s2">&#34;https://example.com/&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">title</span> <span class="p">=</span> <span class="s2">&#34;Example&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">paginate</span> <span class="p">=</span> <span class="mi">25</span>
</span></span><span class="line"><span class="cl"><span class="nx">enableRobotsTXT</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl"><span class="nx">buildDrafts</span> <span class="p">=</span> <span class="kc">false</span>
</span></span><span class="line"><span class="cl"><span class="nx">buildFuture</span> <span class="p">=</span> <span class="kc">false</span>
</span></span><span class="line"><span class="cl"><span class="nx">buildExpired</span> <span class="p">=</span> <span class="kc">false</span>
</span></span><span class="line"><span class="cl"><span class="nx">enableEmoji</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line hl"><span class="cl"><span class="nx">ignorefiles</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;content/slides&#34;</span><span class="p">]</span>
</span></span></code></pre></div><p>So when building the main site it will not build for our slides.</p>
<h3 id="new-slides">New Slides</h3>
<p>Add new slides, assuming we are at the root of our hugo project (where <code>hugo.toml</code> and <code>hugo-slides.toml</code> are).
Notice the <code>contentDir</code> above, hence we put into the <code>slides</code> folder.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir -p content/slides/first-talk
</span></span><span class="line"><span class="cl">touch content/slides/first-talk/_index.md
</span></span></code></pre></div><p>Then in our <code>_index.md</code> file we want to do something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl">+++
</span></span><span class="line"><span class="cl">title = &#34;Reproducible <span class="err">&amp;</span> Ephemeral Development Environments with Nix&#34;
</span></span><span class="line"><span class="cl">outputs = [&#34;Reveal&#34;]
</span></span><span class="line"><span class="cl">+++
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gh"># Hello world!
</span></span></span><span class="line"><span class="cl"><span class="gh"></span>
</span></span><span class="line"><span class="cl">This is my first slide.
</span></span></code></pre></div><p><code>hugo server --config hugo-slides.toml</code>, we can now access the slides at <code>http://localhost:1313/slides/first-talk/#/</code>.
You can look at the <a href="https://github.com/dzello/reveal-hugo">README</a> for more information about syntax.</p>
<h3 id="add-theme">Add Theme</h3>
<p>If we want to add a custom theme to our site we can do something like in our frontmatter:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl">+++
</span></span><span class="line"><span class="cl">title = &#34;Reproducible <span class="err">&amp;</span> Ephemeral Development Environments with Nix&#34;
</span></span><span class="line"><span class="cl">outputs = [&#34;Reveal&#34;]
</span></span><span class="line"><span class="cl">[reveal_hugo]
</span></span><span class="line hl"><span class="cl">custom_theme = &#34;stylesheets/reveal/catppuccin.css&#34;
</span></span><span class="line hl"><span class="cl">slide_number = true
</span></span><span class="line"><span class="cl">+++
</span></span></code></pre></div><p>Where the stylesheet can be found in <code>static/slides/stylesheets/reveal/catppuccin.css</code>.  You could also have a separate
theme for styling, but I put them into a single CSS file to make my life a bit easier. But you may prefer them
in two separate files to make them easier to share between slides.</p>
<p>That&rsquo;s it! We added reveal-hugo to our existing Hugo blog whilst keeping existing syntax highlighting working.
It wasn&rsquo;t the most elegant solution, but as I said earlier it works well enough for me!</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog">My Hugo Blog</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Deploy Kubernetes Dashboard Using Pulumi to a K3s Cluster</title>
      <link>https://haseebmajid.dev/posts/2024-05-24-how-to-deploy-kubernetes-dashboard-using-pulumi-to-a-k3s-cluster/</link>
      <pubDate>Fri, 24 May 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-05-24-how-to-deploy-kubernetes-dashboard-using-pulumi-to-a-k3s-cluster/</guid>
      <description>&lt;details
  class=&#34;notice warn&#34;
  open=&#34;true&#34;
&gt;
    &lt;summary class=&#34;notice-title&#34;&gt;Kubernetes Knowledge&lt;/summary&gt;
  
  I don&amp;rsquo;t know a ton about Kubernetes. I am still learning, hence this home-lab project. So there may be better ways
to do stuff that I have described below. If that is the case, please let me know! Just take what I say with a pinch of
salt.
&lt;/details&gt;

&lt;p&gt;It&amp;rsquo;s been a while since I made an update about my home lab, also an aside, I should&amp;rsquo;ve given this series a better name.
So recently, I finally went back to my PI cluster and started to actually setup properly. The first thing I decided
to put on it was the Kubernetes dashboard. I could get a nice GUI look into what is happening with my cluster at a
glance. It also looked simple to deploy.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<details
  class="notice warn"
  open="true"
>
    <summary class="notice-title">Kubernetes Knowledge</summary>
  
  I don&rsquo;t know a ton about Kubernetes. I am still learning, hence this home-lab project. So there may be better ways
to do stuff that I have described below. If that is the case, please let me know! Just take what I say with a pinch of
salt.
</details>

<p>It&rsquo;s been a while since I made an update about my home lab, also an aside, I should&rsquo;ve given this series a better name.
So recently, I finally went back to my PI cluster and started to actually setup properly. The first thing I decided
to put on it was the Kubernetes dashboard. I could get a nice GUI look into what is happening with my cluster at a
glance. It also looked simple to deploy.</p>
<p>I want to have all my k8s config in code, so I decided to deploy it with helm and Pulumi. One of the nice things about
Pulumi is I can configure resources in a programming language I know, i.e. Go vs having to use Terraform. Which I am
sort of familiar with but no expert.</p>
<p>So, if you have followed my previous posts, we have a working PI k3s cluster running on NixOS. We have also enabled Tailscale
to make it easier to connect to. Now to actually put &ldquo;things&rdquo; on our cluster.</p>
<h2 id="getting-started">Getting Started</h2>
<p>If you are using Nix we can use a flake devshell like so to install Pulumi and some other useful tools. In <code>flake.nix</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">description</span> <span class="o">=</span> <span class="s2">&#34;Developer Shell&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">inputs</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">nixpkgs</span><span class="o">.</span><span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;github:nixos/nixpkgs/nixos-unstable&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">outputs</span> <span class="o">=</span> <span class="p">{</span><span class="n">nixpkgs</span><span class="o">,</span> <span class="o">...</span><span class="p">}:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">devShell</span><span class="o">.</span><span class="n">x86_64-linux</span> <span class="o">=</span> <span class="k">let</span>
</span></span><span class="line"><span class="cl">      <span class="n">pkgs</span> <span class="o">=</span> <span class="n">nixpkgs</span><span class="o">.</span><span class="n">legacyPackages</span><span class="o">.</span><span class="n">x86_64-linux</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">in</span>
</span></span><span class="line"><span class="cl">      <span class="n">pkgs</span><span class="o">.</span><span class="n">mkShell</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">packages</span> <span class="o">=</span> <span class="k">with</span> <span class="n">pkgs</span><span class="p">;</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">          <span class="n">sops</span>
</span></span><span class="line"><span class="cl">          <span class="n">go</span>
</span></span><span class="line hl"><span class="cl">          <span class="n">pulumi</span>
</span></span><span class="line"><span class="cl">          <span class="n">pulumiPackages</span><span class="o">.</span><span class="n">pulumi-language-go</span>
</span></span><span class="line"><span class="cl">          <span class="n">kubernetes-helm</span>
</span></span><span class="line"><span class="cl">          <span class="n">k9s</span>
</span></span><span class="line"><span class="cl">          <span class="n">kubectl</span>
</span></span><span class="line"><span class="cl">        <span class="p">];</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Combined with direnv (<code>.envrc</code>), we can load into this shell automatically when we change directories to this folder.
So we don&rsquo;t need to have these packages installed globally, we can have them just setup when we need them for this project.</p>
<p>However, it doesn&rsquo;t really matter whether you use Nix or not. You just need to work out how to install Pulumi on your machine.
One thing to remember is this is on our development machine, which will connect to our cluster and deploy our config.
We don&rsquo;t need these tools installed on our PI cluster.</p>
<h2 id="pulumi">Pulumi</h2>
<p>We can either use Pulumi locally, i.e. store state locally. Or use their managed service, to make my life easier
I created a Pulumi account. Just note, you don&rsquo;t need to use their propriety managed platform if you don&rsquo;t want to.
It just the easiest way to get started and not have to worry about sharing state between machines.
This includes state so that Pulumi knows what is installed on the k3s cluster.</p>
<h3 id="managed-platform">Managed Platform</h3>
<p>So first create an account or login to Pulumi. Then we need to create a new project on Pulumi I am going to create a
new Kubernetes project like so:</p>
<p><img
        loading="lazy"
        src="/posts/2024-05-24-how-to-deploy-kubernetes-dashboard-using-pulumi-to-a-k3s-cluster/images/pulumi-project.png"
        type=""
        alt="Pulumi Project"
        
      />
<img
        loading="lazy"
        src="/posts/2024-05-24-how-to-deploy-kubernetes-dashboard-using-pulumi-to-a-k3s-cluster/images/pulumi-kubernetes.png"
        type=""
        alt="Pulumi Kubernetes Template"
        
      /></p>
<p>We now have a new project and stack. I still fully understand the difference, but it seems we can have multiple stacks
per project. So perhaps this could be useful if we had different environments, such as dev/prod. But we want to keep
them in the same project.</p>
<p>In our case, we will keep it simple with a project called <code>home-lab</code> and stack called <code>main</code> (I am bad at naming things).
Then go to your home-lab project folder, the one above with the <code>flake.nix</code> at the root. Then run
<code>pulumi new kubernetes-go -s hmajid2301/home-lab/main</code>. Replace the 2nd part with your own username/project/stack.
This will now give us some boilerplate and set up a <code>go.mod</code>, <code>go.sum</code> and <code>main.go</code> file we can use.</p>
<h3 id="test-deploy">Test Deploy</h3>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">KUBECONFIG</summary>
  
  Pulumi will the default KUBECONFIG found typically at <code>~/.kube/config</code>. In my case, I use <code>~/.kube/config.personal</code>
so I set the <code>KUBECONFIG</code> environment variable inside my <code>flake.nix</code> to point to this config file.
</details>

<p>We can then do deploy what is in our <code>main.go</code> with <code>pulumi up</code>, which will deploy a new nginx app.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span> <span class="nx">main</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">	<span class="nx">appsv1</span> <span class="s">&#34;github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apps/v1&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="nx">corev1</span> <span class="s">&#34;github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/core/v1&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="nx">metav1</span> <span class="s">&#34;github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/meta/v1&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;github.com/pulumi/pulumi/sdk/v3/go/pulumi&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">pulumi</span><span class="p">.</span><span class="nf">Run</span><span class="p">(</span><span class="kd">func</span><span class="p">(</span><span class="nx">ctx</span> <span class="o">*</span><span class="nx">pulumi</span><span class="p">.</span><span class="nx">Context</span><span class="p">)</span> <span class="kt">error</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="nx">appLabels</span> <span class="o">:=</span> <span class="nx">pulumi</span><span class="p">.</span><span class="nx">StringMap</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="s">&#34;app&#34;</span><span class="p">:</span> <span class="nx">pulumi</span><span class="p">.</span><span class="nf">String</span><span class="p">(</span><span class="s">&#34;nginx&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">		<span class="nx">deployment</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">appsv1</span><span class="p">.</span><span class="nf">NewDeployment</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="s">&#34;app-dep&#34;</span><span class="p">,</span> <span class="o">&amp;</span><span class="nx">appsv1</span><span class="p">.</span><span class="nx">DeploymentArgs</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="nx">Spec</span><span class="p">:</span> <span class="nx">appsv1</span><span class="p">.</span><span class="nx">DeploymentSpecArgs</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">				<span class="nx">Selector</span><span class="p">:</span> <span class="o">&amp;</span><span class="nx">metav1</span><span class="p">.</span><span class="nx">LabelSelectorArgs</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">					<span class="nx">MatchLabels</span><span class="p">:</span> <span class="nx">appLabels</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">				<span class="p">},</span>
</span></span><span class="line"><span class="cl">				<span class="nx">Replicas</span><span class="p">:</span> <span class="nx">pulumi</span><span class="p">.</span><span class="nf">Int</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">				<span class="nx">Template</span><span class="p">:</span> <span class="o">&amp;</span><span class="nx">corev1</span><span class="p">.</span><span class="nx">PodTemplateSpecArgs</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">					<span class="nx">Metadata</span><span class="p">:</span> <span class="o">&amp;</span><span class="nx">metav1</span><span class="p">.</span><span class="nx">ObjectMetaArgs</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">						<span class="nx">Labels</span><span class="p">:</span> <span class="nx">appLabels</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">					<span class="p">},</span>
</span></span><span class="line"><span class="cl">					<span class="nx">Spec</span><span class="p">:</span> <span class="o">&amp;</span><span class="nx">corev1</span><span class="p">.</span><span class="nx">PodSpecArgs</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">						<span class="nx">Containers</span><span class="p">:</span> <span class="nx">corev1</span><span class="p">.</span><span class="nx">ContainerArray</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">							<span class="nx">corev1</span><span class="p">.</span><span class="nx">ContainerArgs</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">								<span class="nx">Name</span><span class="p">:</span>  <span class="nx">pulumi</span><span class="p">.</span><span class="nf">String</span><span class="p">(</span><span class="s">&#34;nginx&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">								<span class="nx">Image</span><span class="p">:</span> <span class="nx">pulumi</span><span class="p">.</span><span class="nf">String</span><span class="p">(</span><span class="s">&#34;nginx&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">							<span class="p">}},</span>
</span></span><span class="line"><span class="cl">					<span class="p">},</span>
</span></span><span class="line"><span class="cl">				<span class="p">},</span>
</span></span><span class="line"><span class="cl">			<span class="p">},</span>
</span></span><span class="line"><span class="cl">		<span class="p">})</span>
</span></span><span class="line"><span class="cl">		<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="k">return</span> <span class="nx">err</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="nx">ctx</span><span class="p">.</span><span class="nf">Export</span><span class="p">(</span><span class="s">&#34;name&#34;</span><span class="p">,</span> <span class="nx">deployment</span><span class="p">.</span><span class="nx">Metadata</span><span class="p">.</span><span class="nf">Name</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="cl">	<span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="dashboard">Dashboard</h3>
<p>However, we want to deploy the Kubernetes dashboard. So let&rsquo;s do the following, create a new file called <code>dashboard.go</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">addDashboard</span><span class="p">(</span><span class="nx">ctx</span> <span class="o">*</span><span class="nx">pulumi</span><span class="p">.</span><span class="nx">Context</span><span class="p">)</span> <span class="kt">error</span> <span class="p">{}</span>
</span></span></code></pre></div><p>We will call this function from our <code>main.go</code> like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">package</span> <span class="nx">main</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;github.com/pulumi/pulumi/sdk/v3/go/pulumi&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">pulumi</span><span class="p">.</span><span class="nf">Run</span><span class="p">(</span><span class="kd">func</span><span class="p">(</span><span class="nx">ctx</span> <span class="o">*</span><span class="nx">pulumi</span><span class="p">.</span><span class="nx">Context</span><span class="p">)</span> <span class="kt">error</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">err</span> <span class="o">:=</span> <span class="nf">addDashboard</span><span class="p">(</span><span class="nx">ctx</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="k">return</span> <span class="nx">err</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="cl">	<span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Now when we run <code>pulumi up</code>, it will look at our <code>main.go</code> and then call the <code>addDashboard</code> function.
Now let&rsquo;s create all the specific resources we need for the dashboard.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">addDashboard</span><span class="p">(</span><span class="nx">ctx</span> <span class="o">*</span><span class="nx">pulumi</span><span class="p">.</span><span class="nx">Context</span><span class="p">)</span> <span class="kt">error</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">namespace</span> <span class="o">:=</span> <span class="s">&#34;monitoring&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nx">_</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">helmv3</span><span class="p">.</span><span class="nf">NewChart</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="s">&#34;kubernetes-dashboard&#34;</span><span class="p">,</span> <span class="nx">helmv3</span><span class="p">.</span><span class="nx">ChartArgs</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">Chart</span><span class="p">:</span> <span class="nx">pulumi</span><span class="p">.</span><span class="nf">String</span><span class="p">(</span><span class="s">&#34;kubernetes-dashboard&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">		<span class="nx">FetchArgs</span><span class="p">:</span> <span class="nx">helmv3</span><span class="p">.</span><span class="nx">FetchArgs</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="nx">Repo</span><span class="p">:</span> <span class="nx">pulumi</span><span class="p">.</span><span class="nf">String</span><span class="p">(</span><span class="s">`https://kubernetes.github.io/dashboard/`</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">		<span class="p">},</span>
</span></span><span class="line"><span class="cl">		<span class="nx">Namespace</span><span class="p">:</span> <span class="nx">pulumi</span><span class="p">.</span><span class="nf">String</span><span class="p">(</span><span class="nx">namespace</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">	<span class="p">})</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="nx">err</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span></code></pre></div><p>First let&rsquo;s define a namespace variable, this is the namespace in Kubernetes that the dashboard will be deployed to.
I am going to create a new namespace <code>monitoring</code>. Then we install a new helm chart, which provide an easy way to install
applications into Kubernetes.  Whilst also providing us with some options to tweak the application to our liking.</p>
<p>Normally, we could lock our dependencies using a <code>chart.lock</code> file; however, it seems Pulumi does not respect this at
the moment <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>. This is enough to get the dashboard working. We could do a <code>pulumi up</code>.</p>
<p>Then after the deployment check the pods using <code>kubectl get pods -n monitoring</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">&gt; kubectl get pods -n monitoring
</span></span><span class="line"><span class="cl">NAME                                                    READY   STATUS    RESTARTS       AGE
</span></span><span class="line"><span class="cl">kubernetes-dashboard-metrics-scraper-7f6f977dc9-n7mhn   1/1     Running   <span class="m">1</span> <span class="o">(</span>122m ago<span class="o">)</span>   38h
</span></span><span class="line"><span class="cl">kubernetes-dashboard-web-f5d66867c-sk9ms                1/1     Running   <span class="m">1</span> <span class="o">(</span>122m ago<span class="o">)</span>   38h
</span></span><span class="line"><span class="cl">kubernetes-dashboard-auth-9688d4ccf-ntdm6               1/1     Running   <span class="m">1</span> <span class="o">(</span>122m ago<span class="o">)</span>   38h
</span></span><span class="line"><span class="cl">kubernetes-dashboard-kong-75bb76dd5f-k29x7              1/1     Running   <span class="m">1</span> <span class="o">(</span>122m ago<span class="o">)</span>   38h
</span></span><span class="line"><span class="cl">kubernetes-dashboard-api-7dc9dc68df-xrfs7               1/1     Running   <span class="m">2</span> <span class="o">(</span>119m ago<span class="o">)</span>   38h
</span></span></code></pre></div><p>However, to have permissions to login into the dashboard via the web GUI we need to create some more resources.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">addDashboard</span><span class="p">(</span><span class="nx">ctx</span> <span class="o">*</span><span class="nx">pulumi</span><span class="p">.</span><span class="nx">Context</span><span class="p">)</span> <span class="kt">error</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">namespace</span> <span class="o">:=</span> <span class="s">&#34;monitoring&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl">	<span class="nx">_</span><span class="p">,</span> <span class="nx">err</span> <span class="p">=</span> <span class="nx">rbacv1</span><span class="p">.</span><span class="nf">NewClusterRoleBinding</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="s">&#34;cluster-admin-binding&#34;</span><span class="p">,</span> <span class="o">&amp;</span><span class="nx">rbacv1</span><span class="p">.</span><span class="nx">ClusterRoleBindingArgs</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">Metadata</span><span class="p">:</span> <span class="nx">metav1</span><span class="p">.</span><span class="nx">ObjectMetaArgs</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="nx">Name</span><span class="p">:</span> <span class="nx">pulumi</span><span class="p">.</span><span class="nf">String</span><span class="p">(</span><span class="s">&#34;admin-user&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">		<span class="p">},</span>
</span></span><span class="line"><span class="cl">		<span class="nx">RoleRef</span><span class="p">:</span> <span class="nx">rbacv1</span><span class="p">.</span><span class="nx">RoleRefArgs</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="nx">Name</span><span class="p">:</span>     <span class="nx">pulumi</span><span class="p">.</span><span class="nf">String</span><span class="p">(</span><span class="s">&#34;cluster-admin&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">			<span class="nx">ApiGroup</span><span class="p">:</span> <span class="nx">pulumi</span><span class="p">.</span><span class="nf">String</span><span class="p">(</span><span class="s">&#34;rbac.authorization.k8s.io&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">			<span class="nx">Kind</span><span class="p">:</span>     <span class="nx">pulumi</span><span class="p">.</span><span class="nf">String</span><span class="p">(</span><span class="s">&#34;ClusterRole&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">		<span class="p">},</span>
</span></span><span class="line"><span class="cl">		<span class="nx">Subjects</span><span class="p">:</span> <span class="nx">rbacv1</span><span class="p">.</span><span class="nx">SubjectArray</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="nx">rbacv1</span><span class="p">.</span><span class="nx">SubjectArgs</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">				<span class="nx">Kind</span><span class="p">:</span>      <span class="nx">pulumi</span><span class="p">.</span><span class="nf">String</span><span class="p">(</span><span class="s">&#34;ServiceAccount&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">				<span class="nx">Name</span><span class="p">:</span>      <span class="nx">svc</span><span class="p">.</span><span class="nx">Metadata</span><span class="p">.</span><span class="nf">Name</span><span class="p">().</span><span class="nf">Elem</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">				<span class="nx">Namespace</span><span class="p">:</span> <span class="nx">pulumi</span><span class="p">.</span><span class="nf">String</span><span class="p">(</span><span class="nx">namespace</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">			<span class="p">},</span>
</span></span><span class="line"><span class="cl">		<span class="p">},</span>
</span></span><span class="line"><span class="cl">	<span class="p">})</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="nx">err</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>What we are doing here is creating a role binding, for a new admin-user, which has full privilege to modify the cluster.
Likely later we will want to change this to a read-only user. As we are only accessing the dashboard with this user.</p>
<p>You can see the equivalent YAML we are applying here <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">ServiceAccount</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">admin-user</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">kubernetes-dashboard</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">rbac.authorization.k8s.io/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">ClusterRoleBinding</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">admin-user</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">roleRef</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">apiGroup</span><span class="p">:</span><span class="w"> </span><span class="l">rbac.authorization.k8s.io</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">ClusterRole</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">cluster-admin</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">subjects</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">ServiceAccount</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">admin-user</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">kubernetes-dashboard</span><span class="w">
</span></span></span></code></pre></div><p>Then run <code>pulumi up</code>, to create these resources again.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Copy JWT to clipboard that we use to login</span>
</span></span><span class="line"><span class="cl">kubectl -n monitoring create token admin-user <span class="p">|</span> wl-copy
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Setup port forwarding so we can access the proxy pod locally on httsp://localhost:8443</span>
</span></span><span class="line"><span class="cl">kubectl -n monitoring port-forward services/kubernetes-dashboard-kong-proxy 8443:443
</span></span></code></pre></div><p>After that, we can get a secret token, we will need to log in to the dashboard. Then we port forward to the proxy pod,
so we can now access the dashboard at <code>https://localhost:8443</code>. Ignore the SSL/TLS warnings, as the certificates are
self-signed. I will likely do a post about how we can fix this later, once I have figured it out myself.</p>
<p><img
        loading="lazy"
        src="/posts/2024-05-24-how-to-deploy-kubernetes-dashboard-using-pulumi-to-a-k3s-cluster/images/login.png"
        type=""
        alt="Login"
        
      />
<img
        loading="lazy"
        src="/posts/2024-05-24-how-to-deploy-kubernetes-dashboard-using-pulumi-to-a-k3s-cluster/images/dashboard.png"
        type=""
        alt="Dashboard"
        
      /></p>
<p>That&rsquo;s it! We&rsquo;ve deployed something to our Kubernetes cluster, woo-hoo!!!!</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://github.com/pulumi/pulumi-kubernetes/issues/2669">https://github.com/pulumi/pulumi-kubernetes/issues/2669</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://upcloud.com/resources/tutorials/deploy-kubernetes-dashboard">https://upcloud.com/resources/tutorials/deploy-kubernetes-dashboard</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Use Env Variables With Viper Config Library in Go</title>
      <link>https://haseebmajid.dev/posts/2024-05-19-how-to-use-env-variables-with-viper-config-library-in-go/</link>
      <pubDate>Sun, 19 May 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-05-19-how-to-use-env-variables-with-viper-config-library-in-go/</guid>
      <description>&lt;p&gt;This is part of a series of where I am going to blog about issues I had building my CLI tool OptiNix and documenting
how I resolved those issues. Most will be random things not specifically related to building CLI tools.&lt;/p&gt;
&lt;p&gt;In this example, we will use the &lt;a href=&#34;https://github.com/spf13/viper&#34;&gt;viper&lt;/a&gt; library. Mainly because I am already using cobra, the library to help us make CLI tools.
From the same author and wanted to see how well they integrated. The Viper config library is probably over kill
in my case.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>This is part of a series of where I am going to blog about issues I had building my CLI tool OptiNix and documenting
how I resolved those issues. Most will be random things not specifically related to building CLI tools.</p>
<p>In this example, we will use the <a href="https://github.com/spf13/viper">viper</a> library. Mainly because I am already using cobra, the library to help us make CLI tools.
From the same author and wanted to see how well they integrated. The Viper config library is probably over kill
in my case.</p>
<p>In my CLI tool, I wanted the tool to be able to see ENV variables to overwrite certain functionality. This made it much
easier to test my app and also gives more flexibility to the end user.
However, I also wanted to have all of my config in a struct. For example, take a simplified version of the config of
OptiNix.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">Sources</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">NixOSURL</span>       <span class="kt">string</span> <span class="s">`mapstructure:&#34;nixos_url&#34;`</span>
</span></span><span class="line"><span class="cl">	<span class="nx">HomeManagerURL</span> <span class="kt">string</span> <span class="s">`mapstructure:&#34;home_manager_url&#34;`</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">Config</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">DBFolder</span> <span class="kt">string</span>  <span class="s">`mapstructure:&#34;db_folder&#34;`</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Sources</span>  <span class="nx">Sources</span> <span class="s">`mapstructure:&#34;sources&#34;`</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>How can we update this using an environment variable, say we wanted to set the database folder path using an env variable called?
<code>OPTINIX_DB_FOLDER</code>, this code shows how he could do this and then return a struct of type <code>Config</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">LoadConfig</span><span class="p">()</span> <span class="p">(</span><span class="o">*</span><span class="nx">Config</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">config</span> <span class="o">:=</span> <span class="o">&amp;</span><span class="nx">Config</span><span class="p">{}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nx">viper</span><span class="p">.</span><span class="nf">SetEnvPrefix</span><span class="p">(</span><span class="s">&#34;optinix&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="nx">viper</span><span class="p">.</span><span class="nf">SetEnvKeyReplacer</span><span class="p">(</span><span class="nx">strings</span><span class="p">.</span><span class="nf">NewReplacer</span><span class="p">(</span><span class="s">`.`</span><span class="p">,</span> <span class="s">`_`</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">	<span class="nx">viper</span><span class="p">.</span><span class="nf">AutomaticEnv</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">viper</span><span class="p">.</span><span class="nf">ReadInConfig</span><span class="p">();</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">if</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">ok</span> <span class="o">:=</span> <span class="nx">err</span><span class="p">.(</span><span class="nx">viper</span><span class="p">.</span><span class="nx">ConfigFileNotFoundError</span><span class="p">);</span> <span class="p">!</span><span class="nx">ok</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="k">return</span> <span class="nx">config</span><span class="p">,</span> <span class="nx">err</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nx">viper</span><span class="p">.</span><span class="nf">SetDefault</span><span class="p">(</span><span class="s">&#34;db_folder&#34;</span><span class="p">,</span> <span class="s">&#34;testfolder&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="nx">err</span> <span class="p">=</span> <span class="nx">viper</span><span class="p">.</span><span class="nf">Unmarshal</span><span class="p">(</span><span class="nx">config</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="nx">config</span><span class="p">,</span> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;unable to decode into config struct, %v&#34;</span><span class="p">,</span> <span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="nx">config</span><span class="p">,</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>We can then access the folder like <code>config.DBFolder</code>. Let&rsquo;s break this code down a bit to understand what is happening.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">viper</span><span class="p">.</span><span class="nf">SetEnvPrefix</span><span class="p">(</span><span class="s">&#34;optinix&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">viper</span><span class="p">.</span><span class="nf">SetEnvKeyReplacer</span><span class="p">(</span><span class="nx">strings</span><span class="p">.</span><span class="nf">NewReplacer</span><span class="p">(</span><span class="s">`.`</span><span class="p">,</span> <span class="s">`_`</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="nx">viper</span><span class="p">.</span><span class="nf">AutomaticEnv</span><span class="p">()</span>
</span></span></code></pre></div><p>This first part is all around loading in env variables to viper. What we are doing here is first loading env variables
that start with <code>OPTINIX</code>. Using the prefix means we don&rsquo;t have to specify it in each of our config options reducing
boilerplate. However, it also means then our env variables will not conflict with other apps and services.</p>
<p>The next two lines means that we can import environment variables correctly. This is mainly important for the nested
struct we have, i.e. <code>sources</code>. We can set the sources like so <code>OPTINIX_SOURCES_NIXOS_URL</code>. Without the replacement,
it would something like <code>OPTINIX_SOURCES.</code>. The final line loads the env variables into viper.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="k">if</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">viper</span><span class="p">.</span><span class="nf">ReadInConfig</span><span class="p">();</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">ok</span> <span class="o">:=</span> <span class="nx">err</span><span class="p">.(</span><span class="nx">viper</span><span class="p">.</span><span class="nx">ConfigFileNotFoundError</span><span class="p">);</span> <span class="p">!</span><span class="nx">ok</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nx">config</span><span class="p">,</span> <span class="nx">err</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">viper</span><span class="p">.</span><span class="nf">SetDefault</span><span class="p">(</span><span class="s">&#34;db_folder&#34;</span><span class="p">,</span> <span class="s">&#34;testfolder&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">viper</span><span class="p">.</span><span class="nf">SetDefault</span><span class="p">(</span><span class="s">&#34;sources.nixos_url&#34;</span><span class="p">,</span> <span class="s">&#34;example.com/nix.hmtl&#34;</span><span class="p">)</span>
</span></span></code></pre></div><p>Then we load all the config options into Viper. One thing to note is viper can also read config from files as well.
So this part handles that. Then finally, if the database folder is not set via env variable, we give it a default value.
We can see with the nested struct <code>sources</code> we use a <code>.</code>, to specify the field inside that struct.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">err</span> <span class="p">=</span> <span class="nx">viper</span><span class="p">.</span><span class="nf">Unmarshal</span><span class="p">(</span><span class="nx">config</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">config</span><span class="p">,</span> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;unable to decode into config struct, %v&#34;</span><span class="p">,</span> <span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">return</span> <span class="nx">config</span><span class="p">,</span> <span class="kc">nil</span>
</span></span></code></pre></div><p>Then in the final part of this function, we unmarshal the viper config into our config struct and return that config.
We can then call this in our <code>main.go</code> and pass the config around to the relevant parts of our code.</p>
<p>Not only does this make our code easier to test, as we can pass in config and set tests to use a local URL, for example.
Maybe a mock server in a Docker container. Then also provides flexibility to our user of the CLI.</p>
<p>That&rsquo;s it! We can now use environment variables to fill in our config struct using the viper config library.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Pull Submodules in a Nix Derivation</title>
      <link>https://haseebmajid.dev/posts/2024-05-12-til-how-to-pull-submodules-in-a-nix-derivation/</link>
      <pubDate>Sun, 12 May 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-05-12-til-how-to-pull-submodules-in-a-nix-derivation/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Pull Submodules in a Nix Derivation&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Recently, I was trying to create a derivation which needed to pull git submodules as well. I was getting an error which
look something like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;data/meson.build:76:0: ERROR: Nonexistent build file &lt;span class=&#34;s1&#34;&gt;&amp;#39;data/submodules/meson.build&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It was coming from this derivation &lt;a href=&#34;https://gitlab.com/hmajid2301/dotfiles/-/blob/c153de146a3bf9339cbef013ac65bc32e6305c8e/packages/gradience/default.nix&#34;&gt;https://gitlab.com/hmajid2301/dotfiles/-/blob/c153de146a3bf9339cbef013ac65bc32e6305c8e/packages/gradience/default.nix&lt;/a&gt; for building the latest version of gradience.&lt;/p&gt;
&lt;p&gt;It turns out when we do a &lt;code&gt;fetchFromGitHub&lt;/code&gt; we need to explicitly tell it to also pull submodules, which makes sense.
So I was able to fix this. I had to add the &lt;code&gt;fetchSubmodules&lt;/code&gt; argument:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Pull Submodules in a Nix Derivation</strong></p>
<p>Recently, I was trying to create a derivation which needed to pull git submodules as well. I was getting an error which
look something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">data/meson.build:76:0: ERROR: Nonexistent build file <span class="s1">&#39;data/submodules/meson.build&#39;</span>
</span></span></code></pre></div><p>It was coming from this derivation <a href="https://gitlab.com/hmajid2301/dotfiles/-/blob/c153de146a3bf9339cbef013ac65bc32e6305c8e/packages/gradience/default.nix">https://gitlab.com/hmajid2301/dotfiles/-/blob/c153de146a3bf9339cbef013ac65bc32e6305c8e/packages/gradience/default.nix</a> for building the latest version of gradience.</p>
<p>It turns out when we do a <code>fetchFromGitHub</code> we need to explicitly tell it to also pull submodules, which makes sense.
So I was able to fix this. I had to add the <code>fetchSubmodules</code> argument:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">pkgs</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">lib</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">fetchFromGitHub</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}:</span>
</span></span><span class="line"><span class="cl"><span class="n">pkgs</span><span class="o">.</span><span class="n">python3Packages</span><span class="o">.</span><span class="n">buildPythonApplication</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">pname</span> <span class="o">=</span> <span class="s2">&#34;gradience&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">version</span> <span class="o">=</span> <span class="s2">&#34;0.8.0-beta1&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">src</span> <span class="o">=</span> <span class="n">fetchFromGitHub</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">owner</span> <span class="o">=</span> <span class="s2">&#34;GradienceTeam&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">repo</span> <span class="o">=</span> <span class="s2">&#34;Gradience&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">rev</span> <span class="o">=</span> <span class="s2">&#34;90b774174da0e3c6b5314e38226bea653a5bf57a&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">sha256</span> <span class="o">=</span> <span class="s2">&#34;sha256-C0GV6vOEZ0wTaKO7BgGuFvHsHeaVwH0W1U8yKUMrO9c=&#34;</span><span class="p">;</span>
</span></span><span class="line hl"><span class="cl">    <span class="n">fetchSubmodules</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>That&rsquo;s it! A simple fix, but took me longer than it should&rsquo;ve done to figure it out.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Part 5B: Installing Our Nix Configuration (NixOS Anywhere) as Part of Your Workflow</title>
      <link>https://haseebmajid.dev/posts/2024-05-02-part-5b-installing-our-nix-configuration-as-part-of-your-workflow/</link>
      <pubDate>Thu, 02 May 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-05-02-part-5b-installing-our-nix-configuration-as-part-of-your-workflow/</guid>
      <description>&lt;p&gt;I wanted to do a short article showing you how you can use your Nix config to install NixOS on a new device. Have your
device setup with NixOS in a few commands with all the packages and tooling you want.&lt;/p&gt;
&lt;p&gt;Previously I was creating my own ISO and then burning that to a USB and using that USB as live media to install my config.
I had a &lt;a href=&#34;https://gitlab.com/hmajid2301/dotfiles/-/blob/3371d7774c133a36d4986a9dcaeb972d4e4638a2/hosts/iso/configuration.nix#L73&#34;&gt; custom script &lt;/a&gt;
&lt;code&gt;nix_installer&lt;/code&gt;, which would run automatically when you loaded into the gnome shell.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I wanted to do a short article showing you how you can use your Nix config to install NixOS on a new device. Have your
device setup with NixOS in a few commands with all the packages and tooling you want.</p>
<p>Previously I was creating my own ISO and then burning that to a USB and using that USB as live media to install my config.
I had a <a href="https://gitlab.com/hmajid2301/dotfiles/-/blob/3371d7774c133a36d4986a9dcaeb972d4e4638a2/hosts/iso/configuration.nix#L73"> custom script </a>
<code>nix_installer</code>, which would run automatically when you loaded into the gnome shell.</p>
<p>However, I think I have found a slightly better way to do this, recently (earlier this week). This is the process I used
recently to reinstall my workstation. We will be doing this using this really cool tool
<a href="https://github.com/nix-community/nixos-anywhere">nixos-anywhere</a>. Which involves installing our config using SSH.</p>
<blockquote>
<p>Note you can still generate an ISO using the snowfall library, but I haven&rsquo;t tested it yet with my config.</p>
</blockquote>
<h2 id="what-do-you-need">What do you need?</h2>
<ul>
<li>Nix configuration: The nix configuration we want to install</li>
<li>Two devices</li>
<li>Target device: My personal desktop, this is where we will install the config onto
<ul>
<li>Need to be able to connect to this from the source device</li>
</ul>
</li>
<li>Source device: My laptop, which can connect to my desktop to install the config</li>
<li>Disko configuration: So we know how to partition our disk(s)</li>
</ul>
<h2 id="disko">Disko</h2>
<p>To partition our disks, we can use the fantastic <a href="https://github.com/nix-community/disko">disko tool</a>. Which allows us to
declaratively declare how to partition our disk(s), in nix configuration. Having a look at an example, one I have:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">disko</span><span class="o">.</span><span class="n">devices</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">disk</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">nvme0n1</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">type</span> <span class="o">=</span> <span class="s2">&#34;disk&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">device</span> <span class="o">=</span> <span class="s2">&#34;/dev/nvme0n1&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">content</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">type</span> <span class="o">=</span> <span class="s2">&#34;gpt&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">          <span class="n">partitions</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">ESP</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">              <span class="n">label</span> <span class="o">=</span> <span class="s2">&#34;boot&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">              <span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;ESP&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">              <span class="n">size</span> <span class="o">=</span> <span class="s2">&#34;512M&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">              <span class="n">type</span> <span class="o">=</span> <span class="s2">&#34;EF00&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">              <span class="n">content</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">type</span> <span class="o">=</span> <span class="s2">&#34;filesystem&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="n">format</span> <span class="o">=</span> <span class="s2">&#34;vfat&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="n">mountpoint</span> <span class="o">=</span> <span class="s2">&#34;/boot&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="n">mountOptions</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                  <span class="s2">&#34;defaults&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="p">];</span>
</span></span><span class="line"><span class="cl">              <span class="p">};</span>
</span></span><span class="line"><span class="cl">            <span class="p">};</span>
</span></span><span class="line"><span class="cl">            <span class="n">luks</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">              <span class="n">size</span> <span class="o">=</span> <span class="s2">&#34;100%&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">              <span class="n">label</span> <span class="o">=</span> <span class="s2">&#34;luks&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">              <span class="n">content</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">type</span> <span class="o">=</span> <span class="s2">&#34;luks&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;cryptroot&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="n">extraOpenArgs</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">                  <span class="s2">&#34;--allow-discards&#34;</span>
</span></span><span class="line"><span class="cl">                  <span class="s2">&#34;--perf-no_read_workqueue&#34;</span>
</span></span><span class="line"><span class="cl">                  <span class="s2">&#34;--perf-no_write_workqueue&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="p">];</span>
</span></span><span class="line"><span class="cl">                <span class="c1"># https://0pointer.net/blog/unlocking-luks2-volumes-with-tpm2-fido2-pkcs11-security-hardware-on-systemd-248.html</span>
</span></span><span class="line"><span class="cl">                <span class="n">settings</span> <span class="o">=</span> <span class="p">{</span><span class="n">crypttabExtraOpts</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;fido2-device=auto&#34;</span> <span class="s2">&#34;token-timeout=10&#34;</span><span class="p">];};</span>
</span></span><span class="line"><span class="cl">                <span class="n">content</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                  <span class="n">type</span> <span class="o">=</span> <span class="s2">&#34;btrfs&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                  <span class="n">extraArgs</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;-L&#34;</span> <span class="s2">&#34;nixos&#34;</span> <span class="s2">&#34;-f&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">                  <span class="n">subvolumes</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;/root&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                      <span class="n">mountpoint</span> <span class="o">=</span> <span class="s2">&#34;/&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                      <span class="n">mountOptions</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;subvol=root&#34;</span> <span class="s2">&#34;compress=zstd&#34;</span> <span class="s2">&#34;noatime&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">                    <span class="p">};</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;/home&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                      <span class="n">mountpoint</span> <span class="o">=</span> <span class="s2">&#34;/home&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                      <span class="n">mountOptions</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;subvol=home&#34;</span> <span class="s2">&#34;compress=zstd&#34;</span> <span class="s2">&#34;noatime&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">                    <span class="p">};</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;/nix&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                      <span class="n">mountpoint</span> <span class="o">=</span> <span class="s2">&#34;/nix&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                      <span class="n">mountOptions</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;subvol=nix&#34;</span> <span class="s2">&#34;compress=zstd&#34;</span> <span class="s2">&#34;noatime&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">                    <span class="p">};</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;/persist&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                      <span class="n">mountpoint</span> <span class="o">=</span> <span class="s2">&#34;/persist&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                      <span class="n">mountOptions</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;subvol=persist&#34;</span> <span class="s2">&#34;compress=zstd&#34;</span> <span class="s2">&#34;noatime&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">                    <span class="p">};</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;/log&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                      <span class="n">mountpoint</span> <span class="o">=</span> <span class="s2">&#34;/var/log&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                      <span class="n">mountOptions</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;subvol=log&#34;</span> <span class="s2">&#34;compress=zstd&#34;</span> <span class="s2">&#34;noatime&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">                    <span class="p">};</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;/swap&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                      <span class="n">mountpoint</span> <span class="o">=</span> <span class="s2">&#34;/swap&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                      <span class="n">swap</span><span class="o">.</span><span class="n">swapfile</span><span class="o">.</span><span class="n">size</span> <span class="o">=</span> <span class="s2">&#34;64G&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">};</span>
</span></span><span class="line"><span class="cl">                  <span class="p">};</span>
</span></span><span class="line"><span class="cl">                <span class="p">};</span>
</span></span><span class="line"><span class="cl">              <span class="p">};</span>
</span></span><span class="line"><span class="cl">            <span class="p">};</span>
</span></span><span class="line"><span class="cl">          <span class="p">};</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">fileSystems</span><span class="o">.</span><span class="s2">&#34;/persist&#34;</span><span class="o">.</span><span class="n">neededForBoot</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">fileSystems</span><span class="o">.</span><span class="s2">&#34;/var/log&#34;</span><span class="o">.</span><span class="n">neededForBoot</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Here we create a LUKS encrypted using BTRFS. Where we have a bunch of sub volumes intended to be used for impermanence
setup and we even have a swap sub volume.</p>
<p>The cool thing about this it removes the manual partition we would have to-do and now nixos-anywhere can do it for us.
In our system configuration, the disk config is imported like so, in my snowfall-based nix config <code>systems/x86_64-linux/workstation/default.nix</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">pkgs</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">lib</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">imports</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="sr">./hardware-configuration.nix</span>
</span></span><span class="line"><span class="cl">    <span class="sr">./disks.nix</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h2 id="install">Install</h2>
<p>Assuming the target device has no OS on it yet, you can simply boot from the normal NixOS ISO (say gnome). Which provides
us an SSH service we can connect to from our source device (my laptop). You can see more details
<a href="https://github.com/nix-community/nixos-anywhere/blob/main/docs/howtos/no-os.md#installing-on-a-machine-with-no-operating-system">here</a>.</p>
<h3 id="on-target-device">On target device</h3>
<ul>
<li>Boot from live media ISO</li>
<li>Grab IP address <code>ip addr</code>
<ul>
<li>Likely something like <code>192.168.1.8</code> (see output below)</li>
</ul>
</li>
<li>Copy our public SSH keys of the source device and put them into the <code>authorized_keys</code>
<ul>
<li>In my case, I can grab them from GitHub
<ul>
<li><code>mkdir -p ~/.ssh curl https://github.com/hmajid2301.keys &gt; ~/.ssh/authorized_keys</code></li>
</ul>
</li>
</ul>
</li>
<li>Now we can SSH from source to target</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"> ip addr
</span></span><span class="line"><span class="cl">1: lo: &lt;LOOPBACK,UP,LOWER_UP&gt; mtu <span class="m">65536</span> qdisc noqueue state UNKNOWN group default qlen <span class="m">1000</span>
</span></span><span class="line"><span class="cl">    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
</span></span><span class="line"><span class="cl">    inet 127.0.0.1/8 scope host lo
</span></span><span class="line"><span class="cl">       valid_lft forever preferred_lft forever
</span></span><span class="line"><span class="cl">    inet6 ::1/128 scope host noprefixroute
</span></span><span class="line"><span class="cl">       valid_lft forever preferred_lft forever
</span></span><span class="line"><span class="cl">2: enp16s0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu <span class="m">1500</span> qdisc mq state UP group default qlen <span class="m">1000</span>
</span></span><span class="line"><span class="cl">    link/ether 74:56:3c:bb:2f:41 brd ff:ff:ff:ff:ff:ff
</span></span><span class="line hl"><span class="cl">    inet 192.168.1.8/24 brd 192.168.1.255 scope global noprefixroute enp16s0
</span></span><span class="line"><span class="cl">       valid_lft forever preferred_lft forever
</span></span><span class="line"><span class="cl">    inet6 2a0a:ef40:10f9:e01:b554:c3ad:550d:8fcd/64 scope global temporary dynamic
</span></span><span class="line"><span class="cl">       valid_lft 84920sec preferred_lft 2120sec
</span></span><span class="line"><span class="cl">    inet6 2a0a:ef40:10f9:e01:7135:7af1:7f82:9ac/64 scope global dynamic mngtmpaddr noprefixroute
</span></span><span class="line"><span class="cl">       valid_lft 84920sec preferred_lft 2120sec
</span></span><span class="line"><span class="cl">    inet6 fe80::7203:6b95:514e:c231/64 scope link noprefixroute
</span></span><span class="line"><span class="cl">       valid_lft forever preferred_lft forever
</span></span></code></pre></div><h3 id="on-source-device">On source device</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone git@github.com:hmajid2301/dotfiles.git ~/dotfiles/
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> dotfiles
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">nix develop
</span></span><span class="line"><span class="cl">nixos-anywhere -- --flake <span class="s1">&#39;.#workstation&#39;</span> nixos@192.168.1.8
</span></span></code></pre></div><p>Then follow and answer the question it asks you, like the password to use for LUKS. This is basically a really nice way
we can now install NixOS onto a new or existing device. With minimal effort, in my opinion.</p>
<p><img
        loading="lazy"
        src="/posts/2024-05-02-part-5b-installing-our-nix-configuration-as-part-of-your-workflow/images/nixos-infection.jpeg"
        type=""
        alt="nixos infect"
        
      /></p>
<p><a href="https://old.reddit.com/r/NixOS/comments/1bfc9tm/meme_nixos_infection/">credit for meme</a></p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Part 5: Nix as Part of Your Development Workflow</title>
      <link>https://haseebmajid.dev/posts/2024-04-28-part-5-nix-as-part-of-your-development-workflow/</link>
      <pubDate>Sun, 28 Apr 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-04-28-part-5-nix-as-part-of-your-development-workflow/</guid>
      <description>&lt;p&gt;My original plan for this article was to discuss my shell and how I configure it. But I have made some significant changes,
to how I structure my Nix configuration and I wanted to go over Why I did that.&lt;/p&gt;
&lt;p&gt;I mean, likely this will probably happen a lot, as my configuration changes more often than it should 🙈.
Anyway, into the main topic.&lt;/p&gt;
&lt;p&gt;My &lt;a href=&#34;https://gitlab.com/hmajid2301/dotfiles/-/tree/590f2329d41ac2710d149bbe14425c79e49c2784&#34;&gt;dotfiles&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;snowfall-what&#34;&gt;Snowfall what?&lt;/h2&gt;
&lt;p&gt;I &lt;a href=&#34;https://gitlab.com/hmajid2301/dotfiles/-/tags/snowfall&#34;&gt;recently&lt;/a&gt; ported my Nix configuration (dotfiles), to use the
&lt;a href=&#34;https://snowfall.org/guides/lib/quickstart/&#34;&gt;snowfall-lib&lt;/a&gt; to structure my Nix config.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>My original plan for this article was to discuss my shell and how I configure it. But I have made some significant changes,
to how I structure my Nix configuration and I wanted to go over Why I did that.</p>
<p>I mean, likely this will probably happen a lot, as my configuration changes more often than it should 🙈.
Anyway, into the main topic.</p>
<p>My <a href="https://gitlab.com/hmajid2301/dotfiles/-/tree/590f2329d41ac2710d149bbe14425c79e49c2784">dotfiles</a></p>
<h2 id="snowfall-what">Snowfall what?</h2>
<p>I <a href="https://gitlab.com/hmajid2301/dotfiles/-/tags/snowfall">recently</a> ported my Nix configuration (dotfiles), to use the
<a href="https://snowfall.org/guides/lib/quickstart/">snowfall-lib</a> to structure my Nix config.</p>
<p>It is an opinionated library that I think removes a ton of boilerplate from my Nix configuration. I like having
my code structured, and I like not having to think about it much.</p>
<p>One thing I really enjoy is I don&rsquo;t need to import all of my modules and config. Snowfall handles this all for us.
We will see a before and after for one of my NixOS system configuration files. Basically less boilerplate, as I said above.</p>
<h2 id="structure">Structure</h2>
<p>Let&rsquo;s have a look at the structure and explain briefly what&rsquo;s in it</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">.
</span></span><span class="line"><span class="cl">├── flake.lock
</span></span><span class="line"><span class="cl">├── flake.nix
</span></span><span class="line"><span class="cl">├── homes
</span></span><span class="line"><span class="cl">│  ├── x86-64-install-iso
</span></span><span class="line"><span class="cl">│  └── x86_64-linux
</span></span><span class="line"><span class="cl">├── lib
</span></span><span class="line"><span class="cl">│  └── module
</span></span><span class="line"><span class="cl">├── modules
</span></span><span class="line"><span class="cl">│  ├── home
</span></span><span class="line"><span class="cl">│  │  ├── browsers
</span></span><span class="line"><span class="cl">│  │  ├── cli
</span></span><span class="line"><span class="cl">│  │  ├── desktops
</span></span><span class="line"><span class="cl">│  │  ├── programs
</span></span><span class="line"><span class="cl">│  │  ├── secrets.yaml
</span></span><span class="line"><span class="cl">│  │  ├── security
</span></span><span class="line"><span class="cl">│  │  ├── services
</span></span><span class="line"><span class="cl">│  │  ├── suites
</span></span><span class="line"><span class="cl">│  │  ├── systems
</span></span><span class="line"><span class="cl">│  │  └── user
</span></span><span class="line"><span class="cl">│  └── nixos
</span></span><span class="line"><span class="cl">│     ├── cli
</span></span><span class="line"><span class="cl">│     ├── hardware
</span></span><span class="line"><span class="cl">│     ├── secrets.yaml
</span></span><span class="line"><span class="cl">│     ├── security
</span></span><span class="line"><span class="cl">│     ├── services
</span></span><span class="line"><span class="cl">│     ├── suites
</span></span><span class="line"><span class="cl">│     ├── system
</span></span><span class="line"><span class="cl">│     └── user
</span></span><span class="line"><span class="cl">├── packages
</span></span><span class="line"><span class="cl">├── shells
</span></span><span class="line"><span class="cl">└── systems
</span></span><span class="line"><span class="cl">   ├── x86_64-install-iso
</span></span><span class="line"><span class="cl">   └── x86_64-linux
</span></span><span class="line"><span class="cl">      ├── framework
</span></span><span class="line"><span class="cl">      ├── vm
</span></span><span class="line"><span class="cl">      └── workstation
</span></span></code></pre></div><ul>
<li>flake.nix: Entry point for the configuration</li>
<li>homes: home manager configuration for each device</li>
<li>modules: Specific Nix configuration, anything shared between multiple devices split into home-manager and NixOS modules</li>
<li>packages: Nix packages specific to me, some of these are those not available on nixpkgs yet. Some are specific to me, like wallpaper or fonts</li>
<li>shell: The devshell for this project</li>
<li>systems: The NixOS configuration for each device</li>
</ul>
<p>Taking a deeper dive into my configuration into what is going on each folder.</p>
<h3 id="modules">Modules</h3>
<p>The main part of my configuration, contains all the re-usable bits of my config. That can be shared, between
multiple devices. Let&rsquo;s see what I mean.</p>
<p>First of all, it is broken down into two parts, one for my NixOS specific config and one for home-manager. As before
I try to put as much of my config into the <code>modules/home</code> part because it means I can configure more of my machine
using Nix that doesn&rsquo;t use NixOS. Like my Ubuntu work laptop.</p>
<h4 id="nixos">NixOS</h4>
<p>I tried to split into various sub folders relating to what that config is related to, for example CLI tooling.
<code>moudles/nixos/cli/programs/nh/default.nix</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">config</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">lib</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}:</span>
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="n">lib</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="n">lib</span><span class="o">.</span><span class="n">nixicle</span><span class="p">;</span> <span class="k">let</span>
</span></span><span class="line"><span class="cl">  <span class="n">cfg</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">cli</span><span class="o">.</span><span class="n">programs</span><span class="o">.</span><span class="n">nh</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">in</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">options</span><span class="o">.</span><span class="n">cli</span><span class="o">.</span><span class="n">programs</span><span class="o">.</span><span class="n">nh</span> <span class="o">=</span> <span class="k">with</span> <span class="n">types</span><span class="p">;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="n">mkBoolOpt</span> <span class="no">false</span> <span class="s2">&#34;Whether or not to enable nh.&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">config</span> <span class="o">=</span> <span class="n">mkIf</span> <span class="n">cfg</span><span class="o">.</span><span class="n">enable</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">programs</span><span class="o">.</span><span class="n">nh</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">clean</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">clean</span><span class="o">.</span><span class="n">extraArgs</span> <span class="o">=</span> <span class="s2">&#34;--keep-since 4d --keep 3&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">flake</span> <span class="o">=</span> <span class="s2">&#34;/home/</span><span class="si">${</span><span class="n">config</span><span class="o">.</span><span class="n">user</span><span class="o">.</span><span class="n">name</span><span class="si">}</span><span class="s2">/dotfiles&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This is the general format of all of my files. Where we need to manually enable all the various modules we want to use.
To reduce boilerplate because often similar devices will want the same modules, think of them as &ldquo;features&rdquo;. I have
the concept of <code>suites</code>. I don&rsquo;t have many suites, but if we have a look at the common and desktop suites as an example.
<code>modules/nixos/suites/common/default.nix</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">lib</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">config</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}:</span>
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="n">lib</span><span class="p">;</span> <span class="k">let</span>
</span></span><span class="line"><span class="cl">  <span class="n">cfg</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">suites</span><span class="o">.</span><span class="n">common</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">in</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">options</span><span class="o">.</span><span class="n">suites</span><span class="o">.</span><span class="n">common</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="n">mkEnableOption</span> <span class="s2">&#34;Enable common configuration&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">config</span> <span class="o">=</span> <span class="n">mkIf</span> <span class="n">cfg</span><span class="o">.</span><span class="n">enable</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">nix</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">hardware</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">audio</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">bluetooth</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">networking</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">services</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">openssh</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">security</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">sops</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">yubikey</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">system</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">boot</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">plymouth</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">fonts</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">locale</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>These are modules that most of my devices will enable and use. Which enable modules similar to the one we saw for <code>nh</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">lib</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">config</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}:</span>
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="n">lib</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="n">lib</span><span class="o">.</span><span class="n">nixicle</span><span class="p">;</span> <span class="k">let</span>
</span></span><span class="line"><span class="cl">  <span class="n">cfg</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">suites</span><span class="o">.</span><span class="n">desktop</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">in</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">options</span><span class="o">.</span><span class="n">suites</span><span class="o">.</span><span class="n">desktop</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="n">mkEnableOption</span> <span class="s2">&#34;Enable desktop configuration&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">config</span> <span class="o">=</span> <span class="n">mkIf</span> <span class="n">cfg</span><span class="o">.</span><span class="n">enable</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">suites</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">common</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">desktop</span><span class="o">.</span><span class="n">addons</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">nautilus</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">hardware</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">logitechMouse</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">zsa</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">services</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">nixicle</span><span class="o">.</span><span class="n">avahi</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">backup</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">vpn</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">virtualisation</span><span class="o">.</span><span class="n">podman</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">cli</span><span class="o">.</span><span class="n">programs</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">nh</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">nix-ld</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">user</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;haseeb&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">initialPassword</span> <span class="o">=</span> <span class="s2">&#34;1&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Then we can also see, the <code>desktop</code> suite using the common suite and extending with more config modules I will want.
Like enabling Podman, backups and a VPN. Things I want across all of my Desktops.</p>
<p>That&rsquo;s the main bit! These are just modules that are then imported, and we will see this a bit later. The NixOS stuff
doesn&rsquo;t tend to change much, and it mostly the same across all of my devices that run NixOS.</p>
<h4 id="home">home</h4>
<p>The main bit of my config as with my old config relates to home-manager, again so I can use this config also on non
NixOS devices. The structure here is much the same. Except there is a lot more choice and modules not turned on.</p>
<p>Such as <code>modules/home/cli/terminals/</code> contains all the terminals I could use on my device. Though usually, we only
have one enabled at a time, but the choice is there if we want it.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">  alacritty/
</span></span><span class="line"><span class="cl">  foot/
</span></span><span class="line"><span class="cl">  kitty/
</span></span><span class="line"><span class="cl">  wezterm/
</span></span></code></pre></div><p>Each config looks pretty similar to other ones we saw above:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">config</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">lib</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}:</span>
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="n">lib</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="n">lib</span><span class="o">.</span><span class="n">nixicle</span><span class="p">;</span> <span class="k">let</span>
</span></span><span class="line"><span class="cl">  <span class="n">cfg</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">cli</span><span class="o">.</span><span class="n">terminals</span><span class="o">.</span><span class="n">foot</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">in</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">options</span><span class="o">.</span><span class="n">cli</span><span class="o">.</span><span class="n">terminals</span><span class="o">.</span><span class="n">foot</span> <span class="o">=</span> <span class="k">with</span> <span class="n">types</span><span class="p">;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="n">mkBoolOpt</span> <span class="no">false</span> <span class="s2">&#34;enable foot terminal emulator&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">config</span> <span class="o">=</span> <span class="n">mkIf</span> <span class="n">cfg</span><span class="o">.</span><span class="n">enable</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">programs</span><span class="o">.</span><span class="n">foot</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">catppuccin</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">settings</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">main</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">term</span> <span class="o">=</span> <span class="s2">&#34;foot&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">          <span class="n">font</span> <span class="o">=</span> <span class="s2">&#34;MonoLisa Nerd Font:size=14; Noto Color Emoji:size=20&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">          <span class="n">shell</span> <span class="o">=</span> <span class="s2">&#34;fish&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">          <span class="n">pad</span> <span class="o">=</span> <span class="s2">&#34;30x30&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">          <span class="n">selection-target</span> <span class="o">=</span> <span class="s2">&#34;clipboard&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">scrollback</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">lines</span> <span class="o">=</span> <span class="mi">10000</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>We can enable it using <code>cli.terminals.foot.enable = true;</code>, in our home-manager config. We also have a bunch of suites
we can use with this config.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">  common/
</span></span><span class="line"><span class="cl">  desktop/
</span></span><span class="line"><span class="cl">  development/
</span></span><span class="line"><span class="cl">  gaming/
</span></span><span class="line"><span class="cl">  guis/
</span></span><span class="line"><span class="cl">  streaming/
</span></span></code></pre></div><p>We can turn them on depending on the device. Such as on my work laptop, I will not use the gaming suite. If we look
at the <code>modules/home/suites/development/default.nix</code> file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">lib</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">config</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}:</span>
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="n">lib</span><span class="p">;</span> <span class="k">let</span>
</span></span><span class="line"><span class="cl">  <span class="n">cfg</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">suites</span><span class="o">.</span><span class="n">development</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">in</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">options</span><span class="o">.</span><span class="n">suites</span><span class="o">.</span><span class="n">development</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="n">mkEnableOption</span> <span class="s2">&#34;Enable development configuration&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">config</span> <span class="o">=</span> <span class="n">mkIf</span> <span class="n">cfg</span><span class="o">.</span><span class="n">enable</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">suites</span><span class="o">.</span><span class="n">common</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">cli</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">editors</span><span class="o">.</span><span class="n">nvim</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">multiplexers</span><span class="o">.</span><span class="n">zellij</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">programs</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">attic</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">atuin</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">bat</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">bottom</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">direnv</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">eza</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">fzf</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">git</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">gpg</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">k8s</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">modern-unix</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">network-tools</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">nix-index</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">podman</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">ssh</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">starship</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">yazi</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">zoxide</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>We can see here I am enabling most of the CLI tooling I want available one by one. This allows us to turn them off
on certain machines, if we want to overwrite this, in a specific device config.</p>
<h5 id="neovim">Neovim</h5>
<p>A decent part of my home-manager config is configuring Neovim. I use Neovim btw!!!! ;) And I use NixOS btw!!!! And I
used to use Arch btw!!!! Okay, with those important details out of the way. As I said before, I had a bunch of imports
but now in each folder we have a <code>default.nix</code> which contains this import:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">imports</span> <span class="o">=</span> <span class="n">lib</span><span class="o">.</span><span class="n">snowfall</span><span class="o">.</span><span class="n">fs</span><span class="o">.</span><span class="n">get-non-default-nix-files</span> <span class="sr">./.</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>So we may have something <code>nvim/editor/default.nix</code> and this will import everything in <code>nvim/editor/</code> folder.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">  default.nix
</span></span><span class="line"><span class="cl">  focus.nix
</span></span><span class="line"><span class="cl">  telescope.nix
</span></span><span class="line"><span class="cl">  trouble.nix
</span></span></code></pre></div><h3 id="shell">Shell</h3>
<p>We can set up development shells as well, for example to create a default devshell we can do this at <code>shells/default/default.nix</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span><span class="n">pkgs</span><span class="o">,</span> <span class="o">...</span><span class="p">}:</span> <span class="k">let</span>
</span></span><span class="line"><span class="cl">  <span class="n">json2nix</span> <span class="o">=</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">writeScriptBin</span> <span class="s2">&#34;json2nix&#34;</span> <span class="s1">&#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">    </span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">python3</span><span class="si">}</span><span class="s1">/bin/python </span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">fetchurl</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;https://gitlab.com/-/snippets/3613708/raw/main/json2nix.py&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">hash</span> <span class="o">=</span> <span class="s2">&#34;sha256-zZeL3JwwD8gmrf+fG/SPP51vOOUuhsfcQuMj6HNfppU=&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span><span class="si">}</span><span class="s1"> $@
</span></span></span><span class="line"><span class="cl"><span class="s1">  &#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">yaml2nix</span> <span class="o">=</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">writeScriptBin</span> <span class="s2">&#34;yaml2nix&#34;</span> <span class="s1">&#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">    nix run github:euank/yaml2nix &#39;.args&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">  &#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">in</span>
</span></span><span class="line"><span class="cl">  <span class="n">pkgs</span><span class="o">.</span><span class="n">mkShell</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">NIX_CONFIG</span> <span class="o">=</span> <span class="s2">&#34;extra-experimental-features = nix-command flakes repl-flake&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">packages</span> <span class="o">=</span> <span class="k">with</span> <span class="n">pkgs</span><span class="p">;</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="n">yaml2nix</span>
</span></span><span class="line"><span class="cl">      <span class="n">json2nix</span>
</span></span><span class="line"><span class="cl">      <span class="n">statix</span>
</span></span><span class="line"><span class="cl">      <span class="n">deadnix</span>
</span></span><span class="line"><span class="cl">      <span class="n">alejandra</span>
</span></span><span class="line"><span class="cl">      <span class="n">home-manager</span>
</span></span><span class="line"><span class="cl">      <span class="n">git</span>
</span></span><span class="line"><span class="cl">      <span class="n">sops</span>
</span></span><span class="line"><span class="cl">      <span class="n">ssh-to-age</span>
</span></span><span class="line"><span class="cl">      <span class="n">gnupg</span>
</span></span><span class="line"><span class="cl">      <span class="n">age</span>
</span></span><span class="line"><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span></code></pre></div><p>We can then load into this using <code>nix develop</code>, or use direnv. Where we have a <code>.envrc</code> file with the contents:</p>
<pre tabindex="0"><code>use flake
</code></pre><p>Which will load into our devshell for us when we change into this folder. We will have all of the above packages made
available for this project.</p>
<h3 id="systems">Systems</h3>
<p>These are first split into by architecture, then the hostname of the machine <code>systems/x86_64-linux/workstation/default.nix</code>:</p>
<p>Before, my specific system configuration look something like this</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span> <span class="n">inputs</span><span class="o">,</span> <span class="o">...</span> <span class="p">}:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">imports</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="n">inputs</span><span class="o">.</span><span class="n">hardware</span><span class="o">.</span><span class="n">nixosModules</span><span class="o">.</span><span class="n">framework-12th-gen-intel</span>
</span></span><span class="line"><span class="cl">    <span class="n">inputs</span><span class="o">.</span><span class="n">hyprland</span><span class="o">.</span><span class="n">nixosModules</span><span class="o">.</span><span class="n">default</span>
</span></span><span class="line"><span class="cl">    <span class="n">inputs</span><span class="o">.</span><span class="n">disko</span><span class="o">.</span><span class="n">nixosModules</span><span class="o">.</span><span class="n">disko</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="sr">./hardware-configuration.nix</span>
</span></span><span class="line"><span class="cl">    <span class="sr">./disks.nix</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="sr">../../nixos/global</span>
</span></span><span class="line"><span class="cl">    <span class="sr">../../nixos/users/haseeb.nix</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="sr">../../nixos/optional/backup.nix</span>
</span></span><span class="line"><span class="cl">    <span class="sr">../../nixos/optional/fingerprint.nix</span>
</span></span><span class="line"><span class="cl">    <span class="sr">../../nixos/optional/docker.nix</span>
</span></span><span class="line"><span class="cl">    <span class="sr">../../nixos/optional/fonts.nix</span>
</span></span><span class="line"><span class="cl">    <span class="sr">../../nixos/optional/pipewire.nix</span>
</span></span><span class="line"><span class="cl">    <span class="sr">../../nixos/optional/greetd.nix</span>
</span></span><span class="line"><span class="cl">    <span class="sr">../../nixos/optional/quietboot.nix</span>
</span></span><span class="line"><span class="cl">    <span class="sr">../../nixos/optional/vfio.nix</span>
</span></span><span class="line"><span class="cl">    <span class="sr">../../nixos/optional/vpn.nix</span>
</span></span><span class="line"><span class="cl">    <span class="sr">../../nixos/optional/pam.nix</span>
</span></span><span class="line"><span class="cl">    <span class="sr">../../nixos/optional/grub.nix</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">networking</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">hostName</span> <span class="o">=</span> <span class="s2">&#34;framework&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">system</span><span class="o">.</span><span class="n">stateVersion</span> <span class="o">=</span> <span class="s2">&#34;23.05&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>And now it looks like, we can see far fewer imports, and then I turn on some specific modules to this system.
I like this approach because is it easier to make changes per system if I want. Such as not turning on the gaming suite
on my Laptop, say just my PC.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">pkgs</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">lib</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">imports</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="sr">./hardware-configuration.nix</span>
</span></span><span class="line"><span class="cl">    <span class="sr">./disks.nix</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">services</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">virtualisation</span><span class="o">.</span><span class="n">kvm</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">hardware</span><span class="o">.</span><span class="n">openrgb</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">suites</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">gaming</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">desktop</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">addons</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">hyprland</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">networking</span><span class="o">.</span><span class="n">hostName</span> <span class="o">=</span> <span class="s2">&#34;workstation&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">system</span><span class="o">.</span><span class="n">stateVersion</span> <span class="o">=</span> <span class="s2">&#34;23.11&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="homes">homes</h3>
<p>This is very similar to the systems <code>homes/x86_64-linux/haseeb@workstation/default.nix</code>, now we split using <code>username@hostname</code>.
When we build our NixOS config it will either match on <code>workstation</code> or the username we are logged in as, i.e. <code>haseeb</code>.</p>
<p>Having a look at before and after, here I was already using modules options to enable and disable certain packages/tools.
Unliked in our NixOS specific config above (systems).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">inputs</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">pkgs</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">lib</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">config</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">imports</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="sr">../../home-manager</span>
</span></span><span class="line"><span class="cl">    <span class="sr">../../home-manager/programs/gaming.nix</span>
</span></span><span class="line"><span class="cl">    <span class="sr">../../home-manager/programs/discord</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">config</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">modules</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">browsers</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">firefox</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">editors</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">nvim</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">multiplexers</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">zellij</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">shells</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">fish</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">wms</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">hyprland</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">terminals</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">wezterm</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">my</span><span class="o">.</span><span class="n">settings</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">wallpaper</span> <span class="o">=</span> <span class="s2">&#34;~/dotfiles/home-manager/wallpapers/Kurzgesagt-Galaxy_2.png&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">host</span> <span class="o">=</span> <span class="s2">&#34;desktop&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">default</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">shell</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">fish</span><span class="si">}</span><span class="s2">/bin/fish&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">terminal</span> <span class="o">=</span> <span class="s2">&#34;wezterm&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">browser</span> <span class="o">=</span> <span class="s2">&#34;firefox&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">editor</span> <span class="o">=</span> <span class="s2">&#34;nvim&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">colorscheme</span> <span class="o">=</span> <span class="n">inputs</span><span class="o">.</span><span class="n">nix-colors</span><span class="o">.</span><span class="n">colorSchemes</span><span class="o">.</span><span class="n">catppuccin-mocha</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">home</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">username</span> <span class="o">=</span> <span class="n">lib</span><span class="o">.</span><span class="n">mkDefault</span> <span class="s2">&#34;haseeb&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">homeDirectory</span> <span class="o">=</span> <span class="n">lib</span><span class="o">.</span><span class="n">mkDefault</span> <span class="s2">&#34;/home/</span><span class="si">${</span><span class="n">config</span><span class="o">.</span><span class="n">home</span><span class="o">.</span><span class="n">username</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">stateVersion</span> <span class="o">=</span> <span class="n">lib</span><span class="o">.</span><span class="n">mkDefault</span> <span class="s2">&#34;23.11&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The new version looks something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">cli</span><span class="o">.</span><span class="n">programs</span><span class="o">.</span><span class="n">git</span><span class="o">.</span><span class="n">allowedSigners</span> <span class="o">=</span> <span class="s2">&#34;ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINP5gqbEEj+pykK58djSI1vtMtFiaYcygqhHd3mzPbSt hello@haseebmajid.dev&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">suites</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">desktop</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">gaming</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">streaming</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">desktops</span><span class="o">.</span><span class="n">hyprland</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">nixicle</span><span class="o">.</span><span class="n">user</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;haseeb&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">home</span><span class="o">.</span><span class="n">stateVersion</span> <span class="o">=</span> <span class="s2">&#34;23.11&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Here we could again use something <code>my.settings</code> though so we can change the default terminal in one place and reference
it everywhere. However, we could overwrite some of these setting as well, such as on certain devices I am using an older
version of Hyprland and don&rsquo;t have <code>bindi</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">programs</span><span class="o">.</span><span class="n">waybar</span><span class="o">.</span><span class="n">package</span> <span class="o">=</span> <span class="n">inputs</span><span class="o">.</span><span class="n">waybar</span><span class="o">.</span><span class="n">packages</span><span class="o">.</span><span class="s2">&#34;</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">system</span><span class="si">}</span><span class="s2">&#34;</span><span class="o">.</span><span class="n">waybar</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">wayland</span><span class="o">.</span><span class="n">windowManager</span><span class="o">.</span><span class="n">hyprland</span><span class="o">.</span><span class="n">keyBinds</span><span class="o">.</span><span class="n">bindi</span> <span class="o">=</span> <span class="n">lib</span><span class="o">.</span><span class="n">mkForce</span> <span class="p">{};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Or tying waybar to a specific version of our inputs, again because I&rsquo;m using an older version of Hyprland.</p>
<h2 id="summary">Summary</h2>
<p>So to summarise I migrated my config to use snowfall-lib, which remove boilerplate and gives me a super opinionated
layout for my config. Alongside this, using some of the example config below, I made all of my modules now into something
we need to enable. Making it way easier to turn on &ldquo;features&rdquo; in my nix config.</p>
<p><img
        loading="lazy"
        src="/posts/2024-04-28-part-5-nix-as-part-of-your-development-workflow/images/i-use-nix-btw.jpg"
        type=""
        alt="I use Nix btw"
        
      /></p>
<h2 id="example-configuration">Example Configuration</h2>
<p>Some example configurations that I used as inspiration and to help me update my config.</p>
<ul>
<li><a href="https://github.com/jakehamilton/config">https://github.com/jakehamilton/config</a></li>
<li><a href="https://github.com/IogaMaster/dotfiles">https://github.com/IogaMaster/dotfiles</a></li>
<li><a href="https://github.com/khaneliman/khanelinix">https://github.com/khaneliman/khanelinix</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Part 4b Foot Terminal as Part of Your Development Workflow</title>
      <link>https://haseebmajid.dev/posts/2024-04-25-part-4b-foot-terminal-as-part-of-your-development-workflow/</link>
      <pubDate>Thu, 25 Apr 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-04-25-part-4b-foot-terminal-as-part-of-your-development-workflow/</guid>
      <description>&lt;p&gt;Earlier this year I spoke about using Wezterm as my terminal of choice, however since then, I have swapped back to the
foot terminal emulator. I also have kitty available on my system. However, I don&amp;rsquo;t use it much.&lt;/p&gt;
&lt;p&gt;In this article, I want to add a quick addendum to why I moved away from Wezterm. Note as per that post, this is again
not a super important decision, almost any full colour supported terminal will basically like every other. So if one
work for you, feel free to stick to it.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Earlier this year I spoke about using Wezterm as my terminal of choice, however since then, I have swapped back to the
foot terminal emulator. I also have kitty available on my system. However, I don&rsquo;t use it much.</p>
<p>In this article, I want to add a quick addendum to why I moved away from Wezterm. Note as per that post, this is again
not a super important decision, almost any full colour supported terminal will basically like every other. So if one
work for you, feel free to stick to it.</p>
<h2 id="why-i-swapped">Why I swapped?</h2>
<p>The main reason was I had constant issue with Wezterm on Hyprland playing nice. Within about 6 months it
probably broke 3 times or so. I am not sure where the issue lies, whether with Hyprland, Wezterm or even myself.
Though I did see the author of Hyprland reference, Wezterm not working properly with Wayland. So in the end I decided
to go back to a terminal I had no issues with on Wayland, which was foot. A terminal specifically built to run on
Wayland.</p>
<p>I already had the config in my nix config, so it was mostly a case of doing <code>terminals.foot.enable = true;</code>. Then changing
a few key bindings and I am off.</p>
<h2 id="downsides">Downsides</h2>
<p>The main down-sides with this approach is that foot will only run on Linux machines, whereas Wezterm and other terminals
can run on other operating systems as well. Again I don&rsquo;t plan on not working on a Linux machine, in the near future
so this isn&rsquo;t a big issue for me.</p>
<h2 id="settings">Settings</h2>
<p>Having a look at my config, located somewhere like <code>cli/termains/foot/default.nix</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">config</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">lib</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">programs</span><span class="o">.</span><span class="n">foot</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">catppuccin</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">settings</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">main</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">term</span> <span class="o">=</span> <span class="s2">&#34;foot&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">          <span class="n">font</span> <span class="o">=</span> <span class="s2">&#34;MonoLisa Nerd Font:size=14&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">          <span class="n">shell</span> <span class="o">=</span> <span class="s2">&#34;fish&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">          <span class="n">pad</span> <span class="o">=</span> <span class="s2">&#34;30x30&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">          <span class="n">selection-target</span> <span class="o">=</span> <span class="s2">&#34;clipboard&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">scrollback</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">lines</span> <span class="o">=</span> <span class="mi">10000</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="err">}</span>
</span></span></code></pre></div><p>Again, basic like Wezterm. I am using the catppuccin theme, using the <a href="https://github.com/catppuccin/nix">catppuccin/nix</a>
nix config to theme it. Reducing the boilerplate I have to write. Then I set the font I want, Mono Lisa. Finally set
my default shell and that I want to copy selected text to my clipboard automatically.</p>
<p>Not much more to it tbh! Again, as I say, don&rsquo;t need to be super fancy.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Fix Neorg Metadata Treesitter Issues With Nixvim</title>
      <link>https://haseebmajid.dev/posts/2024-04-21-til-how-to-fix-neorg-metadata-treesitter-issues-with-nixvim/</link>
      <pubDate>Sun, 21 Apr 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-04-21-til-how-to-fix-neorg-metadata-treesitter-issues-with-nixvim/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Fix Neorg Metadata Tree Sitter Issues With NixVim&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you are like me, you may be configuring your Neovim configuration using NixVim. Which is a &amp;ldquo;framework&amp;rdquo; making it
easier to configure Neovim using mostly Nix configuration. Allowing us to keep most of our config in one language,
at least in my case.&lt;/p&gt;
&lt;p&gt;I started using Neorg to manage my notes and to-do lists in my &amp;ldquo;second-brain&amp;rdquo;, but noticed that the metadata part
was not being highlighted as I expected.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Fix Neorg Metadata Tree Sitter Issues With NixVim</strong></p>
<p>If you are like me, you may be configuring your Neovim configuration using NixVim. Which is a &ldquo;framework&rdquo; making it
easier to configure Neovim using mostly Nix configuration. Allowing us to keep most of our config in one language,
at least in my case.</p>
<p>I started using Neorg to manage my notes and to-do lists in my &ldquo;second-brain&rdquo;, but noticed that the metadata part
was not being highlighted as I expected.</p>
<p>My config looked something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">programs</span><span class="o">.</span><span class="n">nixvim</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">plugins</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">neorg</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">lazyLoading</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">modules</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">treesitter</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">grammarPackages</span> <span class="o">=</span> <span class="k">with</span> <span class="n">config</span><span class="o">.</span><span class="n">programs</span><span class="o">.</span><span class="n">nixvim</span><span class="o">.</span><span class="n">plugins</span><span class="o">.</span><span class="n">treesitter</span><span class="o">.</span><span class="n">package</span><span class="o">.</span><span class="n">builtGrammars</span><span class="p">;</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">          <span class="n">norg</span>
</span></span><span class="line"><span class="cl">        <span class="p">];</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Turns out there is another tree sitter package for the n
Neorg metadata, but this is not available with nvim-treesitter.
Hence, also not available with Nixvim. I was able to resolve my issue my using the tree-sitter specific grammar like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">programs</span><span class="o">.</span><span class="n">nixvim</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">plugins</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">neorg</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">lazyLoading</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">modules</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">treesitter</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line hl"><span class="cl">        <span class="n">grammarPackages</span> <span class="o">=</span> <span class="k">with</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">tree-sitter-grammars</span><span class="p">;</span> <span class="p">[</span>
</span></span><span class="line hl"><span class="cl">          <span class="n">tree-sitter-norg</span>
</span></span><span class="line hl"><span class="cl">          <span class="n">tree-sitter-norg-meta</span>
</span></span><span class="line hl"><span class="cl">        <span class="p">];</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>It looks like this. That&rsquo;s it!</p>
<p><img
        loading="lazy"
        src="/posts/2024-04-21-til-how-to-fix-neorg-metadata-treesitter-issues-with-nixvim/images/neorg-metadata.png"
        type=""
        alt="Neorg Metadata"
        
      /></p>
]]></content:encoded>
    </item>
    
    <item>
      <title>My Favourite Neovim Plugins</title>
      <link>https://haseebmajid.dev/posts/2024-04-06-my-favourite-neovim-plugins/</link>
      <pubDate>Sat, 06 Apr 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-04-06-my-favourite-neovim-plugins/</guid>
      <description>&lt;p&gt;In this post, I will go over some of my Neovim plugins I really like that aren&amp;rsquo;t as well known. So I won&amp;rsquo;t really be
talking about telescope, LSP or nvim-cmp. As most users know about these plugins and use them extensively in their
configuration.&lt;/p&gt;
&lt;h2 id=&#34;oilnvim&#34;&gt;oil.nvim&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Link: &lt;a href=&#34;https://github.com/stevearc/oil.nvim&#34;&gt;https://github.com/stevearc/oil.nvim&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;oil.nvim creates a file explorer but as a true vim buffer, so it&amp;rsquo;s effortless to create new files and folders. We
can also move files easily. Again, using all of our normal key bindings we use in Nix.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this post, I will go over some of my Neovim plugins I really like that aren&rsquo;t as well known. So I won&rsquo;t really be
talking about telescope, LSP or nvim-cmp. As most users know about these plugins and use them extensively in their
configuration.</p>
<h2 id="oilnvim">oil.nvim</h2>
<ul>
<li>Link: <a href="https://github.com/stevearc/oil.nvim">https://github.com/stevearc/oil.nvim</a></li>
</ul>
<p>oil.nvim creates a file explorer but as a true vim buffer, so it&rsquo;s effortless to create new files and folders. We
can also move files easily. Again, using all of our normal key bindings we use in Nix.</p>
<p>I realise, many people probably have heard of this plugin, it always seems to appear in the lesser known Neovim
plugin threads on <a href="https://old.reddit.com/r/neovim/comments/1asmozy/what_are_your_favorite_plugins_currently/"> Reddit </a>.
I still think it&rsquo;s just a useful plugin and really fits so well with the vim paradigm. I don&rsquo;t have to learn any new
key bindings really.</p>
<p>Since I use NixVim to configure my Neovim in Nix, my settings look something like this. But you can easily translate
them into their Lua versions.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">oil</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">settings</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">delete_to_trash</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">use_default_keymaps</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">lsp_file_method</span><span class="o">.</span><span class="n">autosave_changes</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">buf_options</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">buflisted</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">bufhidden</span> <span class="o">=</span> <span class="s2">&#34;hide&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">      <span class="n">view_options</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">show_hidden</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Some useful settings I like are,</p>
<ul>
<li><code>show_hidden</code>: so I can see hidden files</li>
<li><code>delete_to_trash</code>: so I can delete files to trash and not germanely</li>
<li><code>lsp_file_method.autosave_changes</code>: Will try to use the LSP to auto-change the names of files we change</li>
</ul>
<p>Overall, a great plugin, highly recommend!!!</p>
<h2 id="headlinesnvim">headlines.nvim</h2>
<p></p>
<ul>
<li>Link: <a href="https://github.com/lukas-reineke/headlines.nvim">https://github.com/lukas-reineke/headlines.nvim</a></li>
</ul>
<p>headlines.nvim, add extra highlighting for our text bases file systems. For me, this means my markdown files and norg
files. I think it makes sections a lot more clear, especially code blocks. It fits in pretty well with the norg concealer
in my opinion.</p>
<p>Not too much more to say about this one, I just like how it looks in my markdown files.</p>
<h2 id="nvim-treesitter-textobjects">nvim-treesitter-textobjects</h2>
<ul>
<li>Link: <a href="https://github.com/nvim-treesitter/nvim-treesitter-textobjects">https://github.com/nvim-treesitter/nvim-treesitter-textobjects</a></li>
</ul>
<p>A good video explaining the plugin: <a href="https://www.youtube.com/watch?v=FuYQ7M73bC0">https://www.youtube.com/watch?v=FuYQ7M73bC0</a></p>
<p>Allows us to select text using tree sitter for example, I can select a function using <code>vaf</code>, or delete a function using
<code>daf</code>. We use the same syntax as we would to say select everything in <code>&quot;</code> <code>va&quot;</code>. Except we use tree sitter objects to do
the matching.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">treesitter-textobjects</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">select</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">keymaps</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;aa&#34;</span> <span class="o">=</span> <span class="s2">&#34;@parameter.outer&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;ia&#34;</span> <span class="o">=</span> <span class="s2">&#34;@parameter.inner&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;af&#34;</span> <span class="o">=</span> <span class="s2">&#34;@function.outer&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;if&#34;</span> <span class="o">=</span> <span class="s2">&#34;@function.inner&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;ac&#34;</span> <span class="o">=</span> <span class="s2">&#34;@class.outer&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;ic&#34;</span> <span class="o">=</span> <span class="s2">&#34;@class.inner&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;ai&#34;</span> <span class="o">=</span> <span class="s2">&#34;@conditional.outer&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;ii&#34;</span> <span class="o">=</span> <span class="s2">&#34;@conditional.inner&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;al&#34;</span> <span class="o">=</span> <span class="s2">&#34;@loop.outer&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;il&#34;</span> <span class="o">=</span> <span class="s2">&#34;@loop.inner&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;ak&#34;</span> <span class="o">=</span> <span class="s2">&#34;@block.outer&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;ik&#34;</span> <span class="o">=</span> <span class="s2">&#34;@block.inner&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;is&#34;</span> <span class="o">=</span> <span class="s2">&#34;@statement.inner&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;as&#34;</span> <span class="o">=</span> <span class="s2">&#34;@statement.outer&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;ad&#34;</span> <span class="o">=</span> <span class="s2">&#34;@comment.outer&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;am&#34;</span> <span class="o">=</span> <span class="s2">&#34;@call.outer&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;im&#34;</span> <span class="o">=</span> <span class="s2">&#34;@call.inner&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="n">move</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">setJumps</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">gotoNextStart</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;]m&#34;</span> <span class="o">=</span> <span class="s2">&#34;@function.outer&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;]]&#34;</span> <span class="o">=</span> <span class="s2">&#34;@class.outer&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">      <span class="n">gotoNextEnd</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;]M&#34;</span> <span class="o">=</span> <span class="s2">&#34;@function.outer&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;][&#34;</span> <span class="o">=</span> <span class="s2">&#34;@class.outer&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">      <span class="n">gotoPreviousStart</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;[m&#34;</span> <span class="o">=</span> <span class="s2">&#34;@function.outer&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;[[&#34;</span> <span class="o">=</span> <span class="s2">&#34;@class.outer&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">      <span class="n">gotoPreviousEnd</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;[M&#34;</span> <span class="o">=</span> <span class="s2">&#34;@function.outer&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;[]&#34;</span> <span class="o">=</span> <span class="s2">&#34;@class.outer&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">swap</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">swapNext</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;)a&#34;</span> <span class="o">=</span> <span class="s2">&#34;@parameter.inner&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">      <span class="n">swapPrevious</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;)A&#34;</span> <span class="o">=</span> <span class="s2">&#34;@parameter.inner&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="err">}</span>
</span></span></code></pre></div><p>I have configured some options here, so we can use it so say select many different objects which do vary language
to language, but I have the main ones I use. I do use the <code>daa</code>, to delete parameters a lot in functions.</p>
<p>I don&rsquo;t really use the move or swap part of this plugin, but I probably should start to leverage that more.
Again, more of something for you to look into and configure yourself.</p>
<p>As an aside, I am becoming a big fun of the mini.nvim plugins so may replace this with <code>mini-ai</code></p>
<h2 id="nvim-navbuddy">nvim-navbuddy</h2>
<ul>
<li>Link: <a href="https://github.com/SmiteshP/nvim-navbuddy">https://github.com/SmiteshP/nvim-navbuddy</a></li>
</ul>
<p>nvim-navbuddy allows us to navigate a file using breadcrumb style navigation. It uses the LSP to help break down
the file into different sections. Such as functions, for loops etc. You can navigate them as if you were using
a file browser, say ranger or yazi.</p>
<p>Again, another plugin I should use more often to navigate in files, especially when I am in a code base I don&rsquo;t know
very well.</p>
<h2 id="nvim-spectre">nvim-spectre</h2>
<ul>
<li>Link: <a href="https://github.com/nvim-pack/nvim-spectre">https://github.com/nvim-pack/nvim-spectre</a></li>
</ul>
<p>
  <img
    loading="lazy"
    src="https://github.com/windwp/nvim-spectre/wiki/assets/demospectre.gif"
    alt="nvim-spectre"
    
  /></p>
<p>Provides us with a panel we can use to search our code and do find and replace. Similar to how you would in say VS Code.
I don&rsquo;t really use this one too much, but when I do it&rsquo;s useful to have. It fits into the vim paradigm pretty well as
well, in my opinion.</p>
<p>That&rsquo;s 4 plugins I &ldquo;use&rdquo; that I think aren&rsquo;t as well known in the vim community, or rather not as popular. I will probably
do a part 2 at some point.</p>
<p>Thanks for reading, please let me know other plugins you like to use on <a href="https://hachyderm.io/@majiy00">mastodon</a>.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Set up Layouts in Zellij That Use Direnv and Nix</title>
      <link>https://haseebmajid.dev/posts/2024-03-18-til-how-to-setup-layouts-in-zellij-that-use-direnv-and-nix/</link>
      <pubDate>Mon, 18 Mar 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-03-18-til-how-to-setup-layouts-in-zellij-that-use-direnv-and-nix/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Set up Layouts in Zellij That Use Direnv and Nix&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I have been using &lt;a href=&#34;https://github.com/zellij-org/zellij&#34;&gt;Zellij&lt;/a&gt; for a while now. I tried to set up layouts for
one of my personal projects. So that we could have tests and linting running and any other tasks we may want
whilst doing development &lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;However, I had an issue working out how to call commands that required direnv and nix to set up development environments.
In my case, my nix dev shell installed the go-task tool to run tasks such as &lt;code&gt;task lint&lt;/code&gt; or &lt;code&gt;task tests&lt;/code&gt;. However
the shell that zellij was running the commands in for the layout did not load my Nix dev shell, so the go-task binary
was not available.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Set up Layouts in Zellij That Use Direnv and Nix</strong></p>
<p>I have been using <a href="https://github.com/zellij-org/zellij">Zellij</a> for a while now. I tried to set up layouts for
one of my personal projects. So that we could have tests and linting running and any other tasks we may want
whilst doing development <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</p>
<p>However, I had an issue working out how to call commands that required direnv and nix to set up development environments.
In my case, my nix dev shell installed the go-task tool to run tasks such as <code>task lint</code> or <code>task tests</code>. However
the shell that zellij was running the commands in for the layout did not load my Nix dev shell, so the go-task binary
was not available.</p>
<p>So to fix this, we need to do something like this <code>layout.kdl</code>:</p>
<pre tabindex="0"><code class="language-kdl" data-lang="kdl">layout {
    pane size=1 borderless=true {
        plugin location=&#34;zellij:tab-bar&#34;
    }
    pane {
        command &#34;fish&#34;
        args &#34;-c&#34; &#34;direnv exec . task lint&#34;
    }
    pane {
        command &#34;fish&#34;
        args &#34;-c&#34; &#34;direnv exec . task tests&#34;
    }
    pane {
        command &#34;docker-compose&#34;
        args &#34;up&#34; &#34;--build -d&#34;
    }
}
</code></pre><p>Where the key being we need to execute the direnv environment i.e. <code>direnv exec .</code>, then we can run commands like
normal. Where direnv <code>.envrc</code> file contains <code>use flake</code>. That&rsquo;s it! If you want Zellij to use direnv and load your
nix flake dev shell, you have to add a bit of boilerplate, which is not ideal but</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://github.com/zellij-org/zellij/issues/2294">https://github.com/zellij-org/zellij/issues/2294</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Get Sway Notification Center to Play Nice With Waybar</title>
      <link>https://haseebmajid.dev/posts/2024-03-15-til-how-to-get-swaync-to-play-nice-with-waybar/</link>
      <pubDate>Fri, 15 Mar 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-03-15-til-how-to-get-swaync-to-play-nice-with-waybar/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Get swaync to Play Nice With Waybar&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I added Sway Notification Center as my notification manager and added a small &amp;ldquo;widget&amp;rdquo; to my Waybar, so I can see how many notifications
I have and silence notifications by clicking on it. However, I found when I opened the swaync sidebar, in my case by
right-clicking on the icon. I found that I could not click on anything else on my Waybar like workspaces. Now I know
I should be using my keyboard, but sometimes it&amp;rsquo;s just easier to use a mouse.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Get swaync to Play Nice With Waybar</strong></p>
<p>I added Sway Notification Center as my notification manager and added a small &ldquo;widget&rdquo; to my Waybar, so I can see how many notifications
I have and silence notifications by clicking on it. However, I found when I opened the swaync sidebar, in my case by
right-clicking on the icon. I found that I could not click on anything else on my Waybar like workspaces. Now I know
I should be using my keyboard, but sometimes it&rsquo;s just easier to use a mouse.</p>
<p>The fix I found was on <a href="https://old.reddit.com/r/swaywm/comments/133cffq/swaync_weird_behavior_on_waybar/">Reddit</a>.
I use nix and configure Waybar using home-manager, the on-click actions now have a small sleep, which I don&rsquo;t even
notice, and this resolves the above issue.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="s2">&#34;custom/notification&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">tooltip</span> <span class="o">=</span> <span class="no">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">format</span> <span class="o">=</span> <span class="s2">&#34;{} {icon}&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;format-icons&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">notification</span> <span class="o">=</span> <span class="s2">&#34;󱅫&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">none</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;dnd-notification&#34;</span> <span class="o">=</span> <span class="s2">&#34; &#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;dnd-none&#34;</span> <span class="o">=</span> <span class="s2">&#34;󰂛&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;inhibited-notification&#34;</span> <span class="o">=</span> <span class="s2">&#34; &#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;inhibited-none&#34;</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;dnd-inhibited-notification&#34;</span> <span class="o">=</span> <span class="s2">&#34; &#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;dnd-inhibited-none&#34;</span> <span class="o">=</span> <span class="s2">&#34; &#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;return-type&#34;</span> <span class="o">=</span> <span class="s2">&#34;json&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;exec-if&#34;</span> <span class="o">=</span> <span class="s2">&#34;which swaync-client&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">exec</span> <span class="o">=</span> <span class="s2">&#34;swaync-client -swb&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;on-click&#34;</span> <span class="o">=</span> <span class="s2">&#34;sleep 0.1 &amp;&amp; swaync-client -t -sw&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;on-click-right&#34;</span> <span class="o">=</span> <span class="s2">&#34;sleep 0.1 &amp;&amp; swaync-client -d -sw&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">escape</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>That&rsquo;s it!</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: Clean Up Tmux Service When Removed by Home Manager</title>
      <link>https://haseebmajid.dev/posts/2024-03-10-til-clean-up-tmux-service-when-removed-by-home-manager/</link>
      <pubDate>Sun, 10 Mar 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-03-10-til-clean-up-tmux-service-when-removed-by-home-manager/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: Clean Up Tmux Service When Removed by Home Manager&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Recently I stopped using tmux to try zellij, however I noticed when I removed tmux from my nix config. I was getting
the following error, when rebuilding my home-manager config:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;The user systemd session is degraded:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  UNIT         LOAD   ACTIVE SUB    DESCRIPTION
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;● tmux.service loaded failed failed tmux default session &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;detached&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Legend: LOAD   → Reflects whether the unit definition was properly loaded.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        ACTIVE → The high-level unit activation state, i.e. generalization of SUB.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        SUB    → The low-level unit activation state, values depend on unit type.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I was wondering where this error was coming from, turns it the symlink to tmux.service has not been deleted that
nix would create.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: Clean Up Tmux Service When Removed by Home Manager</strong></p>
<p>Recently I stopped using tmux to try zellij, however I noticed when I removed tmux from my nix config. I was getting
the following error, when rebuilding my home-manager config:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">The user systemd session is degraded:
</span></span><span class="line"><span class="cl">  UNIT         LOAD   ACTIVE SUB    DESCRIPTION
</span></span><span class="line"><span class="cl">● tmux.service loaded failed failed tmux default session <span class="o">(</span>detached<span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Legend: LOAD   → Reflects whether the unit definition was properly loaded.
</span></span><span class="line"><span class="cl">        ACTIVE → The high-level unit activation state, i.e. generalization of SUB.
</span></span><span class="line"><span class="cl">        SUB    → The low-level unit activation state, values depend on unit type.
</span></span></code></pre></div><p>I was wondering where this error was coming from, turns it the symlink to tmux.service has not been deleted that
nix would create.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ls -al ~/.config/systemd/user/tmux.service
</span></span><span class="line"><span class="cl">Permissions Size User        Group       Date Modified Name
</span></span><span class="line"><span class="cl">.rw-rw-r--   <span class="m">453</span> haseebmajid haseebmajid <span class="m">22</span> Aug  <span class="m">2023</span>   /home/haseebmajid/.config/systemd/user/tmux.service
</span></span></code></pre></div><p>If we look at other systemd services we can see them symlinked to a nix package in the nix store.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ls -al ~/.config/systemd/user
</span></span><span class="line"><span class="cl">lrwxrwxrwx     - haseebmajid haseebmajid <span class="m">20</span> Feb 17:04   spotifyd.service -&gt; /nix/store/7v52bpcyi0zqv012rrmd7s2r742hb2cy-home-manager-files/.config/systemd/user/spotifyd.service
</span></span><span class="line"><span class="cl">lrwxrwxrwx     - haseebmajid haseebmajid <span class="m">20</span> Feb 17:04   swayidle.service -&gt; /nix/store/7v52bpcyi0zqv012rrmd7s2r742hb2cy-home-manager-files/.config/systemd/user/swayidle.service
</span></span></code></pre></div><p>So we can simply remove the tmux file, <code>rm ~/.config/systemd/user/tmux.service</code>. I should look into why this symlink
is not removed.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Run Android Emulator Using avd on Hyprland</title>
      <link>https://haseebmajid.dev/posts/2024-03-02-til-how-to-run-android-emulator-using-avd-on-hyprland/</link>
      <pubDate>Sat, 02 Mar 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-03-02-til-how-to-run-android-emulator-using-avd-on-hyprland/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Run Android Emulator Using avd on Hyprland&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Recently, I was having issues when trying to run an Android Emulator using Android Studio (avd). When I would try to
run an Emulator from the device manager, I would get the following &lt;code&gt;The emulator process for avd xxx has terminated error&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Looking deeper into the logs at &lt;code&gt;~/.local/cache/Google/AndroidStudio2023.1/log/idea.log&lt;/code&gt;, I noticed the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2024-02-20 12:01:45,859 &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt; 781674&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;   INFO - Emulator: Pixel &lt;span class=&#34;m&#34;&gt;7&lt;/span&gt; Pro API &lt;span class=&#34;m&#34;&gt;34&lt;/span&gt; - Android emulator version 33.1.24.0 &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;build_id 11237101&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;CL:N/A&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2024-02-20 12:01:45,859 &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt; 781674&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;   INFO - Emulator: Pixel &lt;span class=&#34;m&#34;&gt;7&lt;/span&gt; Pro API &lt;span class=&#34;m&#34;&gt;34&lt;/span&gt; - Found systemPath /home/haseebmajid/Android/Sdk/system-images/android-34/google_apis_playstore/x86_64/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2024-02-20 12:01:46,037 &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt; 781852&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;   WARN - Emulator: Pixel &lt;span class=&#34;m&#34;&gt;7&lt;/span&gt; Pro API &lt;span class=&#34;m&#34;&gt;34&lt;/span&gt; - Failed to process .ini file /home/haseebmajid/.config/.android/avd/../avd/Pixel_7_Pro_API_34.avd/quickbootChoice.ini &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; reading.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2024-02-20 12:01:46,053 &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt; 781868&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;   INFO - Emulator: Pixel &lt;span class=&#34;m&#34;&gt;7&lt;/span&gt; Pro API &lt;span class=&#34;m&#34;&gt;34&lt;/span&gt; - Storing crashdata in: , detection is enabled &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; process: &lt;span class=&#34;m&#34;&gt;194632&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2024-02-20 12:01:46,054 &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt; 781869&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;   INFO - Emulator: Pixel &lt;span class=&#34;m&#34;&gt;7&lt;/span&gt; Pro API &lt;span class=&#34;m&#34;&gt;34&lt;/span&gt; - Fatal: This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2024-02-20 12:01:46,054 &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt; 781869&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;   INFO - Emulator: Pixel &lt;span class=&#34;m&#34;&gt;7&lt;/span&gt; Pro API &lt;span class=&#34;m&#34;&gt;34&lt;/span&gt; - Duplicate loglines will be removed, &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; you wish to see each individual line launch with the -log-nofilter flag.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2024-02-20 12:01:46,054 &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt; 781869&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;   INFO - Emulator: Pixel &lt;span class=&#34;m&#34;&gt;7&lt;/span&gt; Pro API &lt;span class=&#34;m&#34;&gt;34&lt;/span&gt; - 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2024-02-20 12:01:46,054 &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt; 781869&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;   INFO - Emulator: Pixel &lt;span class=&#34;m&#34;&gt;7&lt;/span&gt; Pro API &lt;span class=&#34;m&#34;&gt;34&lt;/span&gt; - Increasing RAM size to 3072MB
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2024-02-20 12:01:46,054 &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt; 781869&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;   INFO - Emulator: Pixel &lt;span class=&#34;m&#34;&gt;7&lt;/span&gt; Pro API &lt;span class=&#34;m&#34;&gt;34&lt;/span&gt; - Available platform plugins are: xcb.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2024-02-20 12:01:46,054 &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt; 781869&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;   INFO - Emulator: Pixel &lt;span class=&#34;m&#34;&gt;7&lt;/span&gt; Pro API &lt;span class=&#34;m&#34;&gt;34&lt;/span&gt; - Warning: Could not find the Qt platform plugin &lt;span class=&#34;s2&#34;&gt;&amp;#34;wayland&amp;#34;&lt;/span&gt; in &lt;span class=&#34;s2&#34;&gt;&amp;#34;/home/haseebmajid/Android/Sdk/emulator/lib64/qt/plugins&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;((&lt;/span&gt;null&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;:0, &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;null&lt;span class=&#34;o&#34;&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2024-02-20 12:01:46,054 &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt; 781869&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;   INFO - Emulator: Pixel &lt;span class=&#34;m&#34;&gt;7&lt;/span&gt; Pro API &lt;span class=&#34;m&#34;&gt;34&lt;/span&gt; - &lt;span class=&#34;o&#34;&gt;((&lt;/span&gt;null&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;:0, &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;null&lt;span class=&#34;o&#34;&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2024-02-20 12:01:46,055 &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt; 781870&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;   INFO - Emulator: Pixel &lt;span class=&#34;m&#34;&gt;7&lt;/span&gt; Pro API &lt;span class=&#34;m&#34;&gt;34&lt;/span&gt; - Fatal: This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note, I did have qtwayland5 installed on my Ubuntu machine.
So get it working based on this log line &lt;code&gt;Available platform plugins are: xcb.&lt;/code&gt; The simplest thing I could think of was
to run&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Run Android Emulator Using avd on Hyprland</strong></p>
<p>Recently, I was having issues when trying to run an Android Emulator using Android Studio (avd). When I would try to
run an Emulator from the device manager, I would get the following <code>The emulator process for avd xxx has terminated error</code>.</p>
<p>Looking deeper into the logs at <code>~/.local/cache/Google/AndroidStudio2023.1/log/idea.log</code>, I noticed the following:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">2024-02-20 12:01:45,859 <span class="o">[</span> 781674<span class="o">]</span>   INFO - Emulator: Pixel <span class="m">7</span> Pro API <span class="m">34</span> - Android emulator version 33.1.24.0 <span class="o">(</span>build_id 11237101<span class="o">)</span> <span class="o">(</span>CL:N/A<span class="o">)</span>
</span></span><span class="line"><span class="cl">2024-02-20 12:01:45,859 <span class="o">[</span> 781674<span class="o">]</span>   INFO - Emulator: Pixel <span class="m">7</span> Pro API <span class="m">34</span> - Found systemPath /home/haseebmajid/Android/Sdk/system-images/android-34/google_apis_playstore/x86_64/
</span></span><span class="line"><span class="cl">2024-02-20 12:01:46,037 <span class="o">[</span> 781852<span class="o">]</span>   WARN - Emulator: Pixel <span class="m">7</span> Pro API <span class="m">34</span> - Failed to process .ini file /home/haseebmajid/.config/.android/avd/../avd/Pixel_7_Pro_API_34.avd/quickbootChoice.ini <span class="k">for</span> reading.
</span></span><span class="line"><span class="cl">2024-02-20 12:01:46,053 <span class="o">[</span> 781868<span class="o">]</span>   INFO - Emulator: Pixel <span class="m">7</span> Pro API <span class="m">34</span> - Storing crashdata in: , detection is enabled <span class="k">for</span> process: <span class="m">194632</span>
</span></span><span class="line"><span class="cl">2024-02-20 12:01:46,054 <span class="o">[</span> 781869<span class="o">]</span>   INFO - Emulator: Pixel <span class="m">7</span> Pro API <span class="m">34</span> - Fatal: This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.
</span></span><span class="line"><span class="cl">2024-02-20 12:01:46,054 <span class="o">[</span> 781869<span class="o">]</span>   INFO - Emulator: Pixel <span class="m">7</span> Pro API <span class="m">34</span> - Duplicate loglines will be removed, <span class="k">if</span> you wish to see each individual line launch with the -log-nofilter flag.
</span></span><span class="line"><span class="cl">2024-02-20 12:01:46,054 <span class="o">[</span> 781869<span class="o">]</span>   INFO - Emulator: Pixel <span class="m">7</span> Pro API <span class="m">34</span> - 
</span></span><span class="line"><span class="cl">2024-02-20 12:01:46,054 <span class="o">[</span> 781869<span class="o">]</span>   INFO - Emulator: Pixel <span class="m">7</span> Pro API <span class="m">34</span> - Increasing RAM size to 3072MB
</span></span><span class="line"><span class="cl">2024-02-20 12:01:46,054 <span class="o">[</span> 781869<span class="o">]</span>   INFO - Emulator: Pixel <span class="m">7</span> Pro API <span class="m">34</span> - Available platform plugins are: xcb.
</span></span><span class="line"><span class="cl">2024-02-20 12:01:46,054 <span class="o">[</span> 781869<span class="o">]</span>   INFO - Emulator: Pixel <span class="m">7</span> Pro API <span class="m">34</span> - Warning: Could not find the Qt platform plugin <span class="s2">&#34;wayland&#34;</span> in <span class="s2">&#34;/home/haseebmajid/Android/Sdk/emulator/lib64/qt/plugins&#34;</span> <span class="o">((</span>null<span class="o">)</span>:0, <span class="o">(</span>null<span class="o">))</span>
</span></span><span class="line"><span class="cl">2024-02-20 12:01:46,054 <span class="o">[</span> 781869<span class="o">]</span>   INFO - Emulator: Pixel <span class="m">7</span> Pro API <span class="m">34</span> - <span class="o">((</span>null<span class="o">)</span>:0, <span class="o">(</span>null<span class="o">))</span>
</span></span><span class="line"><span class="cl">2024-02-20 12:01:46,055 <span class="o">[</span> 781870<span class="o">]</span>   INFO - Emulator: Pixel <span class="m">7</span> Pro API <span class="m">34</span> - Fatal: This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.
</span></span></code></pre></div><p>Note, I did have qtwayland5 installed on my Ubuntu machine.
So get it working based on this log line <code>Available platform plugins are: xcb.</code> The simplest thing I could think of was
to run</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">QT_QPA_PLATFORM</span><span class="o">=</span>xcb ~/Android/Sdk/emulator/emulator -avd Pixel_XL_API_33
</span></span></code></pre></div><p>Forcing it to use the platform it can find. I don&rsquo;t need to do much Android development, so this was fine for now.
For a better solution, you can look at this <a href="https://github.com/hyprwm/Hyprland/issues/3417">issue</a>, to add both
in your Hyprland config itself.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Load Secret Environment Variables in Fish Shell</title>
      <link>https://haseebmajid.dev/posts/2024-02-28-how-to-load-secret-environment-variables-in-fish-shell/</link>
      <pubDate>Wed, 28 Feb 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-02-28-how-to-load-secret-environment-variables-in-fish-shell/</guid>
      <description>&lt;p&gt;Often you want to load environment variables that are secrets, and you don&amp;rsquo;t want them in your shell history.
Such as your GitHub Access Token or API Token when making requests with curl.&lt;/p&gt;
&lt;p&gt;One easy solution is to load environment variables from a file, say an &lt;code&gt;.env&lt;/code&gt; file. Now, since I am using fish shell,
loading env variables from a file like we do in bash and zsh won’t work, i.e.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Often you want to load environment variables that are secrets, and you don&rsquo;t want them in your shell history.
Such as your GitHub Access Token or API Token when making requests with curl.</p>
<p>One easy solution is to load environment variables from a file, say an <code>.env</code> file. Now, since I am using fish shell,
loading env variables from a file like we do in bash and zsh won’t work, i.e.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"> <span class="nv">GITLAB_PRIVATE_TOKEN</span><span class="o">=</span>an-variable
</span></span></code></pre></div><p>However, this won&rsquo;t work with fish shell, we would need to do something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fish" data-lang="fish"><span class="line"><span class="cl"><span class="k">set</span> <span class="nv">GITLAB_PRIVATE_TOKEN</span> an-variable
</span></span></code></pre></div><p>This issue is that these files won&rsquo;t be computable with other shells, so if, for some reason, you are sharing secrets
file as you may do for a docker-compose file, as I&rsquo;ve done in the past. These are not actual secrets, just used to set up
the docker environment, other devs will be using zsh and bash, so we need to stick to the format above.</p>
<p>We can do if we create a custom function called <code>envsource</code> at <code>~/.config/fish/functions/envsource.fish</code> like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fish" data-lang="fish"><span class="line"><span class="cl"><span class="k">function</span> <span class="nf">envsource</span>
</span></span><span class="line"><span class="cl">  <span class="k">for</span> <span class="nv">line</span> <span class="k">in</span> <span class="o">(</span><span class="nf">cat</span> <span class="nv">$argv</span> <span class="o">|</span> <span class="nf">grep</span> <span class="na">-v</span> <span class="s1">&#39;^#&#39;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">set</span> <span class="nv">item</span> <span class="o">(</span><span class="nb">string </span>split <span class="na">-m</span> <span class="m">1</span> <span class="s1">&#39;=&#39;</span> <span class="nv">$line</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">set</span> <span class="na">-gx</span> <span class="nv">$item</span><span class="o">[</span><span class="m">1</span><span class="o">]</span> <span class="nv">$item</span><span class="o">[</span><span class="m">2</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">    <span class="k">echo</span> <span class="s2">&#34;Exported key </span><span class="nv">$item</span><span class="s2">[1]&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span></code></pre></div><p>Then we can do something like <code>envsource ~/.env</code> to load environment variables.</p>
<h3 id="nix-home-manager">Nix (home-manager)</h3>
<p>In Nix (home-manager) we can do something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="n">programs</span><span class="o">.</span><span class="n">fish</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">functions</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">envsource</span> <span class="o">=</span> <span class="s1">&#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">            for line in (cat $argv | grep -v &#39;^#&#39;)
</span></span></span><span class="line"><span class="cl"><span class="s1">                set item (string split -m 1 &#39;=&#39; $line)
</span></span></span><span class="line"><span class="cl"><span class="s1">                set -gx $item[1] $item[2]
</span></span></span><span class="line"><span class="cl"><span class="s1">                echo &#34;Exported key $item[1]&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">            end
</span></span></span><span class="line"><span class="cl"><span class="s1">        &#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>That&rsquo;s it! We can load environment variables and keep them out of shell history.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li>Function taken from <a href="https://gist.github.com/nikoheikkila/dd4357a178c8679411566ba2ca280fcc">here</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Add New Vim Plugins to nixpkgs Repository</title>
      <link>https://haseebmajid.dev/posts/2024-02-20-til-how-to-add-new-vim-plugins-to-nixpkgs-repository/</link>
      <pubDate>Tue, 20 Feb 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-02-20-til-how-to-add-new-vim-plugins-to-nixpkgs-repository/</guid>
      <description>&lt;p&gt;Recently, I wanted to add a Neovim plugin to nixpkgs, so I can then add it to NixVim. I tried following the guide
from the &lt;a href=&#34;https://github.com/NixOS/nixpkgs/blob/master/doc/languages-frameworks/vim.section.md#adding-new-plugins-to-nixpkgs-adding-new-plugins-to-nixpkgs&#34;&gt;docs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;However, I kept getting the following errors:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;nix-shell -p vimPluginsUpdater --run vim-plugins-updater
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;error:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;       … &lt;span class=&#34;k&#34;&gt;while&lt;/span&gt; calling the &lt;span class=&#34;s1&#34;&gt;&amp;#39;derivationStrict&amp;#39;&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;builtin&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;         at /builtin/derivation.nix:9:12: &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;source&lt;/span&gt; not available&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;       … &lt;span class=&#34;k&#34;&gt;while&lt;/span&gt; evaluating derivation &lt;span class=&#34;s1&#34;&gt;&amp;#39;shell&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;         whose name attribute is located at /nix/store/whhzjfgalghpm34irclh01c0afynmyll-nixpkgs/nixpkgs/pkgs/stdenv/generic/make-derivation.nix:300:7
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;       … &lt;span class=&#34;k&#34;&gt;while&lt;/span&gt; evaluating attribute &lt;span class=&#34;s1&#34;&gt;&amp;#39;buildInputs&amp;#39;&lt;/span&gt; of derivation &lt;span class=&#34;s1&#34;&gt;&amp;#39;shell&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;         at /nix/store/whhzjfgalghpm34irclh01c0afynmyll-nixpkgs/nixpkgs/pkgs/stdenv/generic/make-derivation.nix:347:7:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;          346&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;       &lt;span class=&#34;nv&#34;&gt;depsHostHost&lt;/span&gt;                &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; lib.elemAt &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;lib.elemAt dependencies 1&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 0&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;          347&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;       &lt;span class=&#34;nv&#34;&gt;buildInputs&lt;/span&gt;                 &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; lib.elemAt &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;lib.elemAt dependencies 1&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 1&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;             &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;       ^
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;          348&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;       &lt;span class=&#34;nv&#34;&gt;depsTargetTarget&lt;/span&gt;            &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; lib.elemAt &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;lib.elemAt dependencies 2&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 0&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;       error: undefined variable &lt;span class=&#34;s1&#34;&gt;&amp;#39;vimPluginsUpdater&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;       at «string»:1:107:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            1&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;...&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;@args: with import &amp;lt;nixpkgs&amp;gt; args&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;pkgs.runCommandCC or pkgs.runCommand&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;shell&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;buildInputs&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;vimPluginsUpdater&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Or I was getting 429 network errors, too many requests to GitHub.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Recently, I wanted to add a Neovim plugin to nixpkgs, so I can then add it to NixVim. I tried following the guide
from the <a href="https://github.com/NixOS/nixpkgs/blob/master/doc/languages-frameworks/vim.section.md#adding-new-plugins-to-nixpkgs-adding-new-plugins-to-nixpkgs">docs</a>.</p>
<p>However, I kept getting the following errors:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">nix-shell -p vimPluginsUpdater --run vim-plugins-updater
</span></span><span class="line"><span class="cl">error:
</span></span><span class="line"><span class="cl">       … <span class="k">while</span> calling the <span class="s1">&#39;derivationStrict&#39;</span> <span class="nb">builtin</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">         at /builtin/derivation.nix:9:12: <span class="o">(</span><span class="nb">source</span> not available<span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">       … <span class="k">while</span> evaluating derivation <span class="s1">&#39;shell&#39;</span>
</span></span><span class="line"><span class="cl">         whose name attribute is located at /nix/store/whhzjfgalghpm34irclh01c0afynmyll-nixpkgs/nixpkgs/pkgs/stdenv/generic/make-derivation.nix:300:7
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">       … <span class="k">while</span> evaluating attribute <span class="s1">&#39;buildInputs&#39;</span> of derivation <span class="s1">&#39;shell&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">         at /nix/store/whhzjfgalghpm34irclh01c0afynmyll-nixpkgs/nixpkgs/pkgs/stdenv/generic/make-derivation.nix:347:7:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">          346<span class="p">|</span>       <span class="nv">depsHostHost</span>                <span class="o">=</span> lib.elemAt <span class="o">(</span>lib.elemAt dependencies 1<span class="o">)</span> 0<span class="p">;</span>
</span></span><span class="line"><span class="cl">          347<span class="p">|</span>       <span class="nv">buildInputs</span>                 <span class="o">=</span> lib.elemAt <span class="o">(</span>lib.elemAt dependencies 1<span class="o">)</span> 1<span class="p">;</span>
</span></span><span class="line"><span class="cl">             <span class="p">|</span>       ^
</span></span><span class="line"><span class="cl">          348<span class="p">|</span>       <span class="nv">depsTargetTarget</span>            <span class="o">=</span> lib.elemAt <span class="o">(</span>lib.elemAt dependencies 2<span class="o">)</span> 0<span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">       error: undefined variable <span class="s1">&#39;vimPluginsUpdater&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">       at «string»:1:107:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            1<span class="p">|</span> <span class="o">{</span>...<span class="o">}</span>@args: with import &lt;nixpkgs&gt; args<span class="p">;</span> <span class="o">(</span>pkgs.runCommandCC or pkgs.runCommand<span class="o">)</span> <span class="s2">&#34;shell&#34;</span> <span class="o">{</span> <span class="nv">buildInputs</span> <span class="o">=</span> <span class="o">[</span> <span class="o">(</span>vimPluginsUpdater<span class="o">)</span> <span class="o">]</span><span class="p">;</span> <span class="o">}</span> <span class="s2">&#34;&#34;</span>
</span></span></code></pre></div><p>Or I was getting 429 network errors, too many requests to GitHub.</p>
<h2 id="solution">Solution</h2>
<p>I found this great person who was able to solve my issue on <a href="https://discourse.nixos.org/t/adding-new-neovim-to-nixpkgs/34834/2">discourse</a>.
Assuming you have a fork of nixpkgs and have to clone it locally, i.e. <code>git clone https://github.com/hmajid2301/nixpkgs.git</code></p>
<ol>
<li>Update <code>nixpkgs/pkgs/applications/editors/vim/plugins/vim-plugin-names</code></li>
<li>Can do this using the <code>./update.py add &quot;gbprod/yanky.nvim&quot;</code> (where <code>gbprod/yanky.nvim</code> is the path to git repository)</li>
<li>Export your GitHub Personal Token</li>
<li>You can create one by following <a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic">this link</a></li>
<li><code>export GITHUB_API_TOKEN=your_token</code></li>
<li>I will do a post in the future about how you can do this in fish shell and keep secrets out of your shell history.</li>
<li><code>nix-shell -I nixpkgs=channel:nixpkgs-unstable -p vimPluginsUpdater --run vim-plugins-updater</code></li>
<li>Make sure to run this in the repository root</li>
<li>Push your changes and create a PR with nixpkgs repo</li>
<li>You will likely need to rebase your commits to follow the contribution guidelines, check those when creating the PR</li>
</ol>
<p>That&rsquo;s it! I am not sure if this will be useful for others (I will most likely reference this myself in the future), but
that&rsquo;s the easiest way I found to add new vim plugins to nixpkgs.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Fix Firefox Rendering Bug in Hyprland</title>
      <link>https://haseebmajid.dev/posts/2024-02-15-til-how-to-fix-firefox-rendering-bug-in-hyprland/</link>
      <pubDate>Thu, 15 Feb 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-02-15-til-how-to-fix-firefox-rendering-bug-in-hyprland/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Fix Firefox Rendering Bug in Hyprland&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Recently, I had an issue with my Ubuntu laptop running Hyprland, when running Firefox it had this ugly border around
it. Which looked something like this:&lt;/p&gt;
&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2024-02-15-til-how-to-fix-firefox-rendering-bug-in-hyprland/images/firefox.png&#34;
        type=&#34;&#34;
        alt=&#34;Firefox&#34;
        
      /&gt;&lt;/p&gt;
&lt;p&gt;You can see a weird border around the browser, and if you try to view some of the context menus, they appear small and difficult to read.&lt;/p&gt;
&lt;h2 id=&#34;fix&#34;&gt;Fix&lt;/h2&gt;
&lt;p&gt;As per this &lt;a href=&#34;https://old.reddit.com/r/hyprland/comments/18o8m8q/firefox_croppingbad_rendering/&#34;&gt;Reddit thread&lt;/a&gt; &lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.
There are two fixes, one upgrade to version 0.33 which I could not easily do with Ubuntu. So I used the other fix,&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Fix Firefox Rendering Bug in Hyprland</strong></p>
<p>Recently, I had an issue with my Ubuntu laptop running Hyprland, when running Firefox it had this ugly border around
it. Which looked something like this:</p>
<p><img
        loading="lazy"
        src="/posts/2024-02-15-til-how-to-fix-firefox-rendering-bug-in-hyprland/images/firefox.png"
        type=""
        alt="Firefox"
        
      /></p>
<p>You can see a weird border around the browser, and if you try to view some of the context menus, they appear small and difficult to read.</p>
<h2 id="fix">Fix</h2>
<p>As per this <a href="https://old.reddit.com/r/hyprland/comments/18o8m8q/firefox_croppingbad_rendering/">Reddit thread</a> <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.
There are two fixes, one upgrade to version 0.33 which I could not easily do with Ubuntu. So I used the other fix,</p>
<ul>
<li>Right click on title bar</li>
<li>Select <code>Customize Toolbar..</code></li>
<li>Then check <code>Title Bar</code>
<ul>
<li>You may have to full-screen the app to make it easier to select the title bar</li>
</ul>
</li>
</ul>
<p><img
        loading="lazy"
        src="/posts/2024-02-15-til-how-to-fix-firefox-rendering-bug-in-hyprland/images/title.png"
        type=""
        alt="Title Bar"
        
      /></p>
<h3 id="hyprland-version">Hyprland Version</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">hyprctl version
</span></span><span class="line"><span class="cl">Hyprland, built from branch HEAD at commit 5b8cfdf2efc44106b61e60c642fd964823fd89f3  <span class="o">(</span>props: bump ver to v0.31.0<span class="o">)</span>.
</span></span><span class="line"><span class="cl">Tag: v0.31.0
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">flags: <span class="o">(</span><span class="k">if</span> any<span class="o">)</span>
</span></span><span class="line"><span class="cl">debug
</span></span></code></pre></div><div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Reddit Post with Fix: <a href="https://old.reddit.com/r/hyprland/comments/18o8m8q/firefox_croppingbad_rendering/">https://old.reddit.com/r/hyprland/comments/18o8m8q/firefox_croppingbad_rendering/</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Move Files Between Two Git Repositories and Keep its History</title>
      <link>https://haseebmajid.dev/posts/2024-02-11-til-how-to-move-a-file-between-repos-and-keep-its-git-history/</link>
      <pubDate>Sun, 11 Feb 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-02-11-til-how-to-move-a-file-between-repos-and-keep-its-git-history/</guid>
      <description>&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;p&gt;Recently, I had to move one of my to-do files from its own repository, &lt;code&gt;todo&lt;/code&gt;, to my &lt;code&gt;second-brain&lt;/code&gt; repository.
So I could better keep track of my ideas, and tasks to be done. However, I realised just copying the file over I
would lose all the git history the file had.&lt;/p&gt;
&lt;p&gt;Strictly speaking, I don&amp;rsquo;t need the git history of that file, as the most important thing is the current tasks to be
completed. I could, also, go look in the older repository if I really needed to see the file history. However, I decided it would be a nice exercise to learn how to copy files over and keep their history.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="background">Background</h2>
<p>Recently, I had to move one of my to-do files from its own repository, <code>todo</code>, to my <code>second-brain</code> repository.
So I could better keep track of my ideas, and tasks to be done. However, I realised just copying the file over I
would lose all the git history the file had.</p>
<p>Strictly speaking, I don&rsquo;t need the git history of that file, as the most important thing is the current tasks to be
completed. I could, also, go look in the older repository if I really needed to see the file history. However, I decided it would be a nice exercise to learn how to copy files over and keep their history.</p>
<h2 id="how">How?</h2>
<p>So how can we do just that? In this example, we will copy a file from <code>todo</code> to
<code>second-brain</code>.</p>
<p>So clone both repos i.e.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone git@git.com/todo.git
</span></span><span class="line"><span class="cl">git clone git@git.com/second-brain.git
</span></span></code></pre></div><p>Then we will use the <a href="https://github.com/newren/git-filter-repo">git-filter-repo</a> tool which we can use to rewrite our
git history.</p>
<blockquote>
<p>extract the history of a single directory, src/. This means that only paths under src/ remain in the repo, and any commits that only touched paths outside this directory will be removed. - git filter repo README</p>
</blockquote>
<p>If you are using the Nix package manager, you can do something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">nix-shell -p git-filter-repo
</span></span></code></pre></div><p>Then let&rsquo;s create a new branch on our source repo (<code>todo</code>), then find all commits that are related to the <code>todo.norg</code> file.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">cd</span> todo
</span></span><span class="line"><span class="cl">git checkout -b filter
</span></span><span class="line"><span class="cl">git filter-repo --path todo.norg --refs refs/heads/filter --force
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">git filter-repo --path todo.norg --refs refs/heads/filter --force
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Parsed 69 commits</span>
</span></span><span class="line"><span class="cl"><span class="c1"># New history written in 0.02 seconds...</span>
</span></span><span class="line"><span class="cl"><span class="c1"># HEAD is now at 21e6ae4 chore: Update ToDos</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Completely finished after 0.03 seconds.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">git log
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># commit 21e6ae42c550b2fdd76a1a7c34f8574eb529175d</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Author: Haseeb Majid &lt;hello@haseebmajid.dev&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Date:   Sun Feb 4 22:46:36 2024 +0000</span>
</span></span><span class="line"><span class="cl"><span class="c1"># </span>
</span></span><span class="line"><span class="cl"><span class="c1">#     chore: Update ToDos</span>
</span></span><span class="line"><span class="cl"><span class="c1"># </span>
</span></span><span class="line"><span class="cl"><span class="c1"># commit c0e125be9bddf6666fc321ad3ea1b51e437e4bfc</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Author: Haseeb Majid &lt;hello@haseebmajid.dev&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Date:   Fri Feb 2 22:53:15 2024 +0000</span>
</span></span><span class="line"><span class="cl"><span class="c1"># </span>
</span></span><span class="line"><span class="cl"><span class="c1">#     chore: Update ToDos</span>
</span></span></code></pre></div><p>Then go to our target repo <code>second-brain</code>, we will add the <code>todo</code> repo as a local source to git. Then merge in the changes
on the <code>filter</code> branch. We also need to pass the &ndash;allow-unrelated-histories flag tells Git to allow the merge,
even if the histories of the branches don&rsquo;t share a common commit.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">cd</span> second-brain
</span></span><span class="line"><span class="cl">git checkout -b filter
</span></span><span class="line"><span class="cl">git remote add todo-source ../todo
</span></span><span class="line"><span class="cl">git fetch todo-source
</span></span><span class="line"><span class="cl">git branch todo-source remotes/todo-source/filter
</span></span><span class="line"><span class="cl">git merge todo-source --allow-unrelated-histories
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Merge changes into main branch</span>
</span></span><span class="line"><span class="cl">git checkout main
</span></span><span class="line"><span class="cl">git merge filter
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Delete reference to remote and branch</span>
</span></span><span class="line"><span class="cl">git remote rm todo-source
</span></span><span class="line"><span class="cl">git branch -d filter
</span></span></code></pre></div><p>Now we will have the <code>todo.norg</code> in our <code>second-brain</code> repo with the git history of the file. If we now look at the
git log, we would see all those commits updating the to-do file.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li>Useful Links:
<ul>
<li><a href="https://www.johno.com/move-directory-between-repos-with-git-history">https://www.johno.com/move-directory-between-repos-with-git-history</a></li>
<li><a href="https://blog.billyc.io/how-to-copy-one-or-more-files-from-one-git-repo-to-another-and-keep-the-git-history/">https://blog.billyc.io/how-to-copy-one-or-more-files-from-one-git-repo-to-another-and-keep-the-git-history/</a></li>
</ul>
</li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How To Create A Custom NixOS ISO</title>
      <link>https://haseebmajid.dev/posts/2024-02-04-how-to-create-a-custom-nixos-iso/</link>
      <pubDate>Sun, 04 Feb 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-02-04-how-to-create-a-custom-nixos-iso/</guid>
      <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;In this post, I will show you how you can create a custom NixOS ISO image, using our normal nix configuration as if it
another machine/device. Some of you may be wondering why you want to do that vs using the normal ISO. Particular for
installing on my machines I would like to have my device setup in one go, rather than previously I would install using the
normal ISO, then clone my dot files and build my config again.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>In this post, I will show you how you can create a custom NixOS ISO image, using our normal nix configuration as if it
another machine/device. Some of you may be wondering why you want to do that vs using the normal ISO. Particular for
installing on my machines I would like to have my device setup in one go, rather than previously I would install using the
normal ISO, then clone my dot files and build my config again.</p>
<p>Well, we can skip the first step and have it build my config in my one go during with our custom ISO. We can also
have some applications available to us in the ISO, like a different terminal, i.e. Wezterm. Which makes the experience
slightly nicer.</p>
<p>For those of you wondering, an ISO archive file system which contains files, which can burn onto a disk or typically
USB. Which normally is used to install operating systems.</p>
<h2 id="flakes">Flakes</h2>
<p>I will assume you already have your nix config setup and using flakes. I will be adding a new NixOS configuration
which will then build our ISO.</p>
<p>Within our <code>nixosConfigurations</code>, in our <code>flake.nix</code> file we will add a new section, which I simply named <code>iso</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="n">nixosConfigurations</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line hl"><span class="cl">  <span class="n">iso</span> <span class="o">=</span> <span class="n">lib</span><span class="o">.</span><span class="n">nixosSystem</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">modules</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;</span><span class="si">${</span><span class="n">nixpkgs</span><span class="si">}</span><span class="s2">/nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;</span><span class="si">${</span><span class="n">nixpkgs</span><span class="si">}</span><span class="s2">/nixos/modules/installer/cd-dvd/channel.nix&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="sr">./hosts/iso/configuration.nix</span>
</span></span><span class="line"><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="n">specialArgs</span> <span class="o">=</span> <span class="p">{</span><span class="k">inherit</span> <span class="n">inputs</span> <span class="n">outputs</span><span class="p">;};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">framework</span> <span class="o">=</span> <span class="n">lib</span><span class="o">.</span><span class="n">nixosSystem</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">modules</span> <span class="o">=</span> <span class="p">[</span><span class="sr">./hosts/framework/configuration.nix</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="n">specialArgs</span> <span class="o">=</span> <span class="p">{</span><span class="k">inherit</span> <span class="n">inputs</span> <span class="n">outputs</span><span class="p">;};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>You can see <code>framework</code> is another device (my laptop) nix config. Above it is the config for my nix ISO. There are a
bunch of installers config available <a href="https://github.com/NixOS/nixpkgs/tree/master/nixos/modules/installer/cd-dvd">here</a>.</p>
<p>I will use the graphical gnome config, <code>${nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix</code>
because I am already familiar with gnome and sometimes having a desktop environment can make it
easier to debug and use it also a recovery media if something goes wrong with my NixOS device. We can access a browser
for example. But if you want a smaller more minimal ISO, you can use one of the installers without a GUI, i.e. ones
without <code>graphical</code>.</p>
<p>We also provide with some channels <code>${nixpkgs}/nixos/modules/installer/cd-dvd/channel.nix</code>, so the user doesn&rsquo;t need
to update the channels manually first.</p>
<h2 id="configuration">Configuration</h2>
<p>Then onto the real meat and potatoes, the entry point for our ISO <code>hosts/iso/configuration.nix</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">pkgs</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">lib</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">imports</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="sr">../../nixos</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">nixpkgs</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">hostPlatform</span> <span class="o">=</span> <span class="n">lib</span><span class="o">.</span><span class="n">mkDefault</span> <span class="s2">&#34;x86_64-linux&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">config</span><span class="o">.</span><span class="n">allowUnfree</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">nix</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">settings</span><span class="o">.</span><span class="n">experimental-features</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;nix-command&#34;</span> <span class="s2">&#34;flakes&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="n">extraOptions</span> <span class="o">=</span> <span class="s2">&#34;experimental-features = nix-command flakes&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">services</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">qemuGuest</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">openssh</span><span class="o">.</span><span class="n">settings</span><span class="o">.</span><span class="n">PermitRootLogin</span> <span class="o">=</span> <span class="n">lib</span><span class="o">.</span><span class="n">mkForce</span> <span class="s2">&#34;yes&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">boot</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">kernelPackages</span> <span class="o">=</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">linuxPackages_latest</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">supportedFilesystems</span> <span class="o">=</span> <span class="n">lib</span><span class="o">.</span><span class="n">mkForce</span> <span class="p">[</span><span class="s2">&#34;btrfs&#34;</span> <span class="s2">&#34;reiserfs&#34;</span> <span class="s2">&#34;vfat&#34;</span> <span class="s2">&#34;f2fs&#34;</span> <span class="s2">&#34;xfs&#34;</span> <span class="s2">&#34;ntfs&#34;</span> <span class="s2">&#34;cifs&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">networking</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">hostName</span> <span class="o">=</span> <span class="s2">&#34;iso&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># gnome power settings do not turn off screen</span>
</span></span><span class="line"><span class="cl">  <span class="n">systemd</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">services</span><span class="o">.</span><span class="n">sshd</span><span class="o">.</span><span class="n">wantedBy</span> <span class="o">=</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">lib</span><span class="o">.</span><span class="n">mkForce</span> <span class="p">[</span><span class="s2">&#34;multi-user.target&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="n">targets</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">sleep</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">suspend</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">hibernate</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">hybrid-sleep</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">home-manager</span><span class="o">.</span><span class="n">users</span><span class="o">.</span><span class="n">nixos</span> <span class="o">=</span> <span class="kn">import</span> <span class="sr">./home.nix</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">users</span><span class="o">.</span><span class="n">extraUsers</span><span class="o">.</span><span class="n">root</span><span class="o">.</span><span class="n">password</span> <span class="o">=</span> <span class="s2">&#34;nixos&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>First, I import the global NixOS config I share with all of my NixOS configurations, which include things like PAM auth,
fonts and some common nix settings. I could probably strip out some of the config to trim down the ISO, but I am not
super fused about the total size of the ISO at the moment. I don&rsquo;t even use it very frequently, as I don&rsquo;t need to reinstall
my system very often, now that my config has stabilised.</p>
<p>The rest of my config above is just some basic settings, such as making sure gnome doesn&rsquo;t turn off the screen or suspend
during the installation. The installation can take some time, I didn&rsquo;t like having to manually set the option during the installation.</p>
<h3 id="install-script">Install Script</h3>
<p>To make the above a bit simpler to follow, I removed the pkgs I installed. The main one being the <code>nix_installer</code>, bash
script, which uses the very cool <a href="https://github.com/charmbracelet/gum">gum</a> tool. It makes it effortless to provide
an interactive script. The main thing the script does, it clones my dot files, asks which host to install. Then apply
the disko configuration to partition the disk. Where <a href="https://github.com/nix-community/disko/tree/master">disko</a>,
allows us to declaratively declare how our disk will look, i.e. setting up LUKS, swap file.
<a href="https://gitlab.com/hmajid2301/dotfiles/-/blob/58ac9dade0114aaa29ed73156e790e3a043ab80c/hosts/framework/disks.nix">Here</a>
is a link to an example config for my laptop. Again, I will do a more in-depth post about disko in the future.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="n">environment</span><span class="o">.</span><span class="n">systemPackages</span> <span class="o">=</span> <span class="k">with</span> <span class="n">pkgs</span><span class="p">;</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="n">git</span>
</span></span><span class="line"><span class="cl">    <span class="n">gum</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="n">writeShellScriptBin</span> <span class="s2">&#34;nix_installer&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="s1">&#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">        #!/usr/bin/env bash
</span></span></span><span class="line"><span class="cl"><span class="s1">        set -euo pipefail
</span></span></span><span class="line"><span class="cl"><span class="s1">        gsettings set org.gnome.desktop.session idle-delay 0
</span></span></span><span class="line"><span class="cl"><span class="s1">        gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-ac-type &#39;nothing&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="cl"><span class="s1">        if [ &#34;$(id -u)&#34; -eq 0 ]; then
</span></span></span><span class="line"><span class="cl"><span class="s1">        	echo &#34;ERROR! $(basename &#34;$0&#34;) should be run as a regular user&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">        	exit 1
</span></span></span><span class="line"><span class="cl"><span class="s1">        fi
</span></span></span><span class="line"><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="cl"><span class="s1">        if [ ! -d &#34;$HOME/dotfiles/.git&#34; ]; then
</span></span></span><span class="line"><span class="cl"><span class="s1">        	git clone https://gitlab.com/hmajid2301/dotfiles.git &#34;$HOME/dotfiles&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">        fi
</span></span></span><span class="line"><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="cl"><span class="s1">        TARGET_HOST=$(ls -1 ~/dotfiles/hosts/*/configuration.nix | cut -d&#39;/&#39; -f6 | grep -v iso | gum choose)
</span></span></span><span class="line"><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="cl"><span class="s1">        if [ ! -e &#34;$HOME/dotfiles/hosts/$TARGET_HOST/disks.nix&#34; ]; then
</span></span></span><span class="line"><span class="cl"><span class="s1">        	echo &#34;ERROR! $(basename &#34;$0&#34;) could not find the required $HOME/dotfiles/hosts/$TARGET_HOST/disks.nix&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">        	exit 1
</span></span></span><span class="line"><span class="cl"><span class="s1">        fi
</span></span></span><span class="line"><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="cl"><span class="s1">        gum confirm  --default=false \
</span></span></span><span class="line"><span class="cl"><span class="s1">        &#34;🔥 🔥 🔥 WARNING!!!! This will ERASE ALL DATA on the disk $TARGET_HOST. Are you sure you want to continue?&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="cl"><span class="s1">        echo &#34;Partitioning Disks&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">        sudo nix run github:nix-community/disko \
</span></span></span><span class="line"><span class="cl"><span class="s1">        --extra-experimental-features &#34;nix-command flakes&#34; \
</span></span></span><span class="line"><span class="cl"><span class="s1">        --no-write-lock-file \
</span></span></span><span class="line"><span class="cl"><span class="s1">        -- \
</span></span></span><span class="line"><span class="cl"><span class="s1">        --mode zap_create_mount \
</span></span></span><span class="line"><span class="cl"><span class="s1">        &#34;$HOME/dotfiles/hosts/$TARGET_HOST/disks.nix&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="cl"><span class="s1">        sudo nixos-install --flake &#34;$HOME/dotfiles#$TARGET_HOST&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">      &#39;&#39;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Then finally, we run NixOS install, using the dot files we cloned. Again, the specifics don&rsquo;t matter too much. As this is
my specific ISO, the script is actually run automatically, setup in my home-manager config, when you log in to the ISO.
This happens automatically when the desktop environment loads. I just wanted to show an example of a script. I&rsquo;m pretty
sure, I found something similar script and changed it to fit my needs, but I couldn&rsquo;t find the original &#x1f648;.</p>
<h3 id="home-manager">home-manager</h3>
<p>I also set up home-manager, so I can reuse my <code>home-manager</code> config for my other devices, such as terminal config. Which
fonts to use. Again, just a small thing to make my ISO a bit closer to my normal dev setup. I will have a more in-depth
post about how my home-manager setup works and how I have set up in the future, but for now, I will share briefly
what it looks like without going into too much detail.</p>
<p>Where my home-manager config looks like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">inputs</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">lib</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">config</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">imports</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="sr">../../home-manager</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">config</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">home</span><span class="o">.</span><span class="n">file</span><span class="o">.</span><span class="s2">&#34;.config/autostart/foot.desktop&#34;</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="s1">&#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">      [Desktop Entry]
</span></span></span><span class="line"><span class="cl"><span class="s1">      Type=Application
</span></span></span><span class="line"><span class="cl"><span class="s1">      Exec=foot -m fish -c &#39;nix_installer&#39; 2&gt;&amp;1
</span></span></span><span class="line"><span class="cl"><span class="s1">      Hidden=false
</span></span></span><span class="line"><span class="cl"><span class="s1">      NoDisplay=false
</span></span></span><span class="line"><span class="cl"><span class="s1">      X-GNOME-Autostart-enabled=true
</span></span></span><span class="line"><span class="cl"><span class="s1">      Name[en_NG]=Terminal
</span></span></span><span class="line"><span class="cl"><span class="s1">      Name=Terminal
</span></span></span><span class="line"><span class="cl"><span class="s1">      Comment[en_NG]=Start Terminal On Startup
</span></span></span><span class="line"><span class="cl"><span class="s1">      Comment=Start Terminal On Startup
</span></span></span><span class="line"><span class="cl"><span class="s1">    &#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">modules</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">editors</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">nvim</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">shells</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">fish</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">terminals</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">foot</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">my</span><span class="o">.</span><span class="n">settings</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">host</span> <span class="o">=</span> <span class="s2">&#34;iso&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">default</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">shell</span> <span class="o">=</span> <span class="s2">&#34;fish&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">terminal</span> <span class="o">=</span> <span class="s2">&#34;foot&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">browser</span> <span class="o">=</span> <span class="s2">&#34;firefox&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">editor</span> <span class="o">=</span> <span class="s2">&#34;nvim&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">      <span class="n">fonts</span><span class="o">.</span><span class="n">monospace</span> <span class="o">=</span> <span class="s2">&#34;FiraCode Nerd Font Mono&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">colorscheme</span> <span class="o">=</span> <span class="n">inputs</span><span class="o">.</span><span class="n">nix-colors</span><span class="o">.</span><span class="n">colorSchemes</span><span class="o">.</span><span class="n">catppuccin-mocha</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">home</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">username</span> <span class="o">=</span> <span class="n">lib</span><span class="o">.</span><span class="n">mkDefault</span> <span class="s2">&#34;nixos&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">homeDirectory</span> <span class="o">=</span> <span class="n">lib</span><span class="o">.</span><span class="n">mkDefault</span> <span class="s2">&#34;/home/</span><span class="si">${</span><span class="n">config</span><span class="o">.</span><span class="n">home</span><span class="o">.</span><span class="n">username</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">stateVersion</span> <span class="o">=</span> <span class="n">lib</span><span class="o">.</span><span class="n">mkDefault</span> <span class="s2">&#34;23.05&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Again, this shares config with my common home manager config, here we also have to activate which tools we want to use
such as shells, terminals and some default settings which we probably don&rsquo;t need.
The interesting bit here is the <code>foot.desktop</code> app, which auto-runs and starts the <code>nix_installer</code> script.</p>
<h2 id="build">Build</h2>
<p>Then to build the config, we can run <code>nix build ~/dotfiles#nixosConfigurations.iso.config.system.build.isoImage</code>.
Note, you will have to change the path to your nix config <code>~/dotfiles</code> to where your nix config actually is. The ISO
will be located in the <code>results/</code> folder in the same folder as your nix config.</p>
<p>That&rsquo;s it! We went over how we can create our own custom ISO. Which you can use to speed up setting new machines
to use NixOS. If you like, check out my post about <a href="/posts/2023-09-29-setup-ventoy-on-nixos/">Ventoy</a>, which allows us
to have multiple ISOs on the same USB. A great way to try out multiple OSs.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/dotfiles/-/tree/2901e9d2784cdfb27d7cc70a3dae6657722d4abc/hosts/iso">My nix config</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Get sops-nix Working with home-manager Modules</title>
      <link>https://haseebmajid.dev/posts/2024-01-28-how-to-get-sops-nix-working-with-home-manager-modules/</link>
      <pubDate>Sun, 28 Jan 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-01-28-how-to-get-sops-nix-working-with-home-manager-modules/</guid>
      <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;In this article, I will go over how to get &lt;a href=&#34;https://github.com/Mic92/sops-nix&#34;&gt;sops-nix&lt;/a&gt; to work properly with
home-manager. One issue I noticed was that when I used with home-manager modules/options, I would see a string
like &amp;ldquo;%r/secrets/haseeb/&amp;hellip;&amp;rdquo;. The &lt;code&gt;%r&lt;/code&gt; would not be replaced.&lt;/p&gt;
&lt;p&gt;Relevant Issue: &lt;a href=&#34;https://github.com/Mic92/sops-nix/issues/28&#34;&gt;https://github.com/Mic92/sops-nix/issues/28&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;assumption&#34;&gt;Assumption&lt;/h2&gt;
&lt;p&gt;I will assume you have already setup sops-nix and are using it. In the sense, you have a &lt;code&gt;.sops.yaml&lt;/code&gt; file and are already
using it with NixOS. The instructions in the README is mostly pretty clear how to get setup, but I may in future
create another article how I set up sops-nix. But in this post, we will simply go over how we can make it work with
home-manager. I will also assume you are using nix flakes and have passed sops-nix as an input.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>In this article, I will go over how to get <a href="https://github.com/Mic92/sops-nix">sops-nix</a> to work properly with
home-manager. One issue I noticed was that when I used with home-manager modules/options, I would see a string
like &ldquo;%r/secrets/haseeb/&hellip;&rdquo;. The <code>%r</code> would not be replaced.</p>
<p>Relevant Issue: <a href="https://github.com/Mic92/sops-nix/issues/28">https://github.com/Mic92/sops-nix/issues/28</a></p>
<h2 id="assumption">Assumption</h2>
<p>I will assume you have already setup sops-nix and are using it. In the sense, you have a <code>.sops.yaml</code> file and are already
using it with NixOS. The instructions in the README is mostly pretty clear how to get setup, but I may in future
create another article how I set up sops-nix. But in this post, we will simply go over how we can make it work with
home-manager. I will also assume you are using nix flakes and have passed sops-nix as an input.</p>
<h2 id="solution">Solution</h2>
<p>So I have a file called <code>home-manager/security/sops.nix</code> which looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">inputs</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">pkgs</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">imports</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="n">inputs</span><span class="o">.</span><span class="n">sops-nix</span><span class="o">.</span><span class="n">homeManagerModules</span><span class="o">.</span><span class="n">sops</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">sops</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">gnupg</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">home</span> <span class="o">=</span> <span class="s2">&#34;~/.gnupg&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">sshKeyPaths</span> <span class="o">=</span> <span class="p">[];</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="n">defaultSymlinkPath</span> <span class="o">=</span> <span class="s2">&#34;/run/user/1000/secrets&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">defaultSecretsMountPoint</span> <span class="o">=</span> <span class="s2">&#34;/run/user/1000/secrets.d&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">home</span><span class="o">.</span><span class="n">packages</span> <span class="o">=</span> <span class="k">with</span> <span class="n">pkgs</span><span class="p">;</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="n">sops</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Where the key bit to making it work is the <code>default</code> values here, which tell sops where to mount the secret in tmpfs.
The temporary file-system where are secrets will be stored in files.</p>
<pre tabindex="0"><code>ls -al /run/user/1000/secrets/atuin_key

Permissions Size User   Group Date Modified Name
.r--------   146 haseeb users 28 Jan 09:26   /run/user/1000/secrets/atuin_key
</code></pre><p>Other than that, we install sops so we can use the CLI tool to edit our sops files and add secrets. Where I have
a <code>home-manager/secrets.yaml</code> file for storing all secrets related to home-manager.</p>
<h3 id="atuin">Atuin</h3>
<p>So first I do <code>sops home-manager/secrets.yaml</code>, and add my Atuin secret encryption key to this file.
Having a look at how I use it in one of my modules, say <code>home-manager/programs/atuin.nix</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">config</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">pkgs</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">programs</span><span class="o">.</span><span class="n">atuin</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">settings</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">      <span class="n">key_path</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">sops</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">atuin_key</span><span class="o">.</span><span class="n">path</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">sops</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">atuin_key</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">sopsFile</span> <span class="o">=</span> <span class="sr">../secrets.yaml</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Where we reference this secret in this module. Once this has been built using home manager, if we look at the config
for Atuin that is generated by nix. It will point to that <code>/run/user/1000/secrets/atuin_key</code> file.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to fix being unable to change locations in Mullvad VPN on NixOS</title>
      <link>https://haseebmajid.dev/posts/2024-01-14-til-how-to-fix-unable-to-change-locations-in-mullvad-on-nixos/</link>
      <pubDate>Sun, 14 Jan 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-01-14-til-how-to-fix-unable-to-change-locations-in-mullvad-on-nixos/</guid>
      <description>&lt;p&gt;Recently, I was unable to change the location on my Mullvad VPN from other thing other than sweden. Even using the
mullvad cli tool I would keep getting errors like:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;invalid argument for type conversion: missing custom lists settings
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;it turned out to somehow a mismatch in versions where everything was running 2023.6 but my mullvad cli was using
2023.5. So I ended up fixing this by changing my config to:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Recently, I was unable to change the location on my Mullvad VPN from other thing other than sweden. Even using the
mullvad cli tool I would keep getting errors like:</p>
<pre tabindex="0"><code>invalid argument for type conversion: missing custom lists settings
</code></pre><p>it turned out to somehow a mismatch in versions where everything was running 2023.6 but my mullvad cli was using
2023.5. So I ended up fixing this by changing my config to:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">services</span><span class="o">.</span><span class="n">mullvad-vpn</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">package</span> <span class="o">=</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">mullvad-vpn</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Where setting the package to <code>mullvad-vpn</code>, which then installed the correct version of the mullvad cli tool and I was
then able to change locations using the gui as per usual. No idea why there was a version mismatch.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Fix `Verification Failed` With Ventoy USB</title>
      <link>https://haseebmajid.dev/posts/2024-01-09-til-how-to-fix-verification-failed-with-ventoy-usb/</link>
      <pubDate>Tue, 09 Jan 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-01-09-til-how-to-fix-verification-failed-with-ventoy-usb/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Fix &lt;code&gt;Verification Failed&lt;/code&gt; With Ventoy USB&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Recently, I tried to boot with my Ventoy USB on my new AMD motherboard Framework. However, I was getting a which looked
like &lt;code&gt;Verification failed:(0x1A) Security Violation&lt;/code&gt;. It turns out this was because secure boot was turned on. So we
needed to turn it off initially to boot off the Ventoy. You can follow the instructions
&lt;a href=&#34;https://community.frame.work/t/solved-secure-boot-and-custom-keys-on-the-amd-motherboard/38362/3&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We can then turn on secure boot after, I will do a future post how we can do this with NixOS. When I get it working.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Fix <code>Verification Failed</code> With Ventoy USB</strong></p>
<p>Recently, I tried to boot with my Ventoy USB on my new AMD motherboard Framework. However, I was getting a which looked
like <code>Verification failed:(0x1A) Security Violation</code>. It turns out this was because secure boot was turned on. So we
needed to turn it off initially to boot off the Ventoy. You can follow the instructions
<a href="https://community.frame.work/t/solved-secure-boot-and-custom-keys-on-the-amd-motherboard/38362/3">here</a>.</p>
<p>We can then turn on secure boot after, I will do a future post how we can do this with NixOS. When I get it working.</p>
<p>That&rsquo;s it! Super short post this time.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://askubuntu.com/questions/1456460/verification-failed-0x1a-security-violation-while-installing-ubuntu">SO Post</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Part 4: Wezterm Terminal as Part of Your Development Workflow</title>
      <link>https://haseebmajid.dev/posts/2024-01-05-part-4-wezterm-terminal-as-part-of-your-development-workflow/</link>
      <pubDate>Fri, 05 Jan 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-01-05-part-4-wezterm-terminal-as-part-of-your-development-workflow/</guid>
      <description>&lt;p&gt;I will preface this article by saying, out of all the tools/apps in this series, this is probably the least important
decision you will make. You can use any terminal editor and basically still have the same development workflow as me.
Some common terminal emulators include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;kitty&lt;/li&gt;
&lt;li&gt;alacritty&lt;/li&gt;
&lt;li&gt;foot&lt;/li&gt;
&lt;li&gt;wezterm&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;p&gt;After a break that was probably too long in this series, we&amp;rsquo;re on to the next part looking at our terminal and how we
set it up. Part of the reason was when I started this series I was using alacritty, however after moving to
Hyprland I soon moved over to the foot terminal. The main reason for this was the sixel support with a CLI file manager.
&lt;code&gt;lf&lt;/code&gt;. Which worked with the foot terminal emulator, but I couldn&amp;rsquo;t get working with alacritty. This was so that I could
preview images in the CLI when using &lt;code&gt;lf&lt;/code&gt;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I will preface this article by saying, out of all the tools/apps in this series, this is probably the least important
decision you will make. You can use any terminal editor and basically still have the same development workflow as me.
Some common terminal emulators include:</p>
<ul>
<li>kitty</li>
<li>alacritty</li>
<li>foot</li>
<li>wezterm</li>
</ul>
<h2 id="background">Background</h2>
<p>After a break that was probably too long in this series, we&rsquo;re on to the next part looking at our terminal and how we
set it up. Part of the reason was when I started this series I was using alacritty, however after moving to
Hyprland I soon moved over to the foot terminal. The main reason for this was the sixel support with a CLI file manager.
<code>lf</code>. Which worked with the foot terminal emulator, but I couldn&rsquo;t get working with alacritty. This was so that I could
preview images in the CLI when using <code>lf</code>.</p>
<p>So essentially, the main reason this article is delayed is that I wasn&rsquo;t quite set on the terminal I wanted to use.</p>
<h2 id="why-wezterm">Why Wezterm?</h2>
<p>However, recently, I actually started using Wezterm. There were a few reasons for this, the main being I could configure
Wezterm using Lua. Which also the main language we will use to configure our editor, Neovim. So I favoured
being also able to edit my terminal using the same language <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</p>
<p>Another reason you might want to use Wezterm vs foot is that is cross-platform so if you use multiple OSs you can share
the same terminal emulator, i.e. on macOS, Linux and Windows. Wezterm is also GPU accelerated like kitty and alacritty,
whereas foot is not. However, I don&rsquo;t really do anything in my terminal which needs GPU acceleration to the point I
notice a difference.</p>
<p>Finally, Wezterm does offer some form of session management similar to Tmux. However, it is not as feature rich, as I need
like switching sessions or saving and restoring sessions. That I can easily do in tmux. We will talk about this more
in a future article.</p>
<p>tl;dr: I want to configure my editor in Lua.</p>
<h2 id="configuration">Configuration</h2>
<p>Now onto the real meat and potatoes of this article, how did I configure wezterm?
So I am going to assume you are using NixOS with home-manager as we set up in our previous posts.
We will put our Wezterm config in home-manager so that we can also use the same config for non NixOS machines which
use Nix package manager.</p>
<h3 id="aside-home-manager-organisation">(Aside) Home Manager Organisation</h3>
<p>I have my home-managers module organised such that common apps are kept in the same folder. So I keep all my terminal
configs in one place, i.e. in my case, <code>home-manager/terminals/wezterm</code>. The folder also contains for foot, alacritty.
These all get imported by a main module, then the user can choose in their nix <code>home.nix</code> config which ones to enable
(for use) and which one to set as the default.</p>
<p>So I have <code>home-manager/default.nix</code>, which imports all the terminal configs:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="n">imports</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">  <span class="sr">./browsers/firefox.nix</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="sr">./editors/nvim</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="sr">./multiplexers/tmux.nix</span>
</span></span><span class="line"><span class="cl">  <span class="sr">./multiplexers/zellij</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="sr">./shells/fish.nix</span>
</span></span><span class="line"><span class="cl">  <span class="sr">./shells/nushell.nix</span>
</span></span><span class="line"><span class="cl">  <span class="sr">./shells/zsh.nix</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line hl"><span class="cl">  <span class="sr">./terminals/alacritty.nix</span>
</span></span><span class="line"><span class="cl">  <span class="sr">./terminals/foot.nix</span>
</span></span><span class="line"><span class="cl">  <span class="sr">./terminals/wezterm</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Then in say <code>hosts/framework/home.nix</code>, which is the entry point for my home-manager config:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">config</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">modules</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">terminals</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">wezterm</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h2 id="config">Config</h2>
<p>My <code>home-manager/terminals/wezterm/default.nix</code> looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">config</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">lib</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">pkgs</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}:</span>
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="n">lib</span><span class="p">;</span> <span class="k">let</span>
</span></span><span class="line"><span class="cl">  <span class="n">cfg</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">modules</span><span class="o">.</span><span class="n">terminals</span><span class="o">.</span><span class="n">wezterm</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">in</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">options</span><span class="o">.</span><span class="n">modules</span><span class="o">.</span><span class="n">terminals</span><span class="o">.</span><span class="n">wezterm</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="n">mkEnableOption</span> <span class="s2">&#34;enable wezterm terminal emulator&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">config</span> <span class="o">=</span> <span class="n">mkIf</span> <span class="n">cfg</span><span class="o">.</span><span class="n">enable</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">programs</span><span class="o">.</span><span class="n">wezterm</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">package</span> <span class="o">=</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">wezterm-nightly</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">extraConfig</span> <span class="o">=</span> <span class="nb">builtins</span><span class="o">.</span><span class="n">readFile</span> <span class="sr">./config.lua</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The first part is the boilerplate required for enabling this terminal, if enabled in the nix config options. Then
I pull in extra config from a Lua file. This means I can use the Lua LSP; however, it does mean I cannot pull in
config options from other bits of my nix config. Like my default shell, but for now, I am happy to leave this hard-coded
while I am still tweaking my wezterm config.</p>
<p>Where my Lua config, <code>home-manager/terminals/wezterm/config.lua</code> looks like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="kd">local</span> <span class="n">wezterm</span> <span class="o">=</span> <span class="n">require</span><span class="p">(</span><span class="s2">&#34;wezterm&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="kr">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="n">color_scheme</span> <span class="o">=</span> <span class="s2">&#34;Catppuccin Mocha&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="n">default_prog</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&#34;fish&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">	<span class="n">font</span> <span class="o">=</span> <span class="n">wezterm.font</span><span class="p">(</span><span class="s2">&#34;MonoLisa Nerd Font&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">	<span class="n">font_size</span> <span class="o">=</span> <span class="mf">14.0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="n">enable_tab_bar</span> <span class="o">=</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="n">term</span> <span class="o">=</span> <span class="s2">&#34;wezterm&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="n">keys</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="n">key</span> <span class="o">=</span> <span class="s2">&#34;t&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">			<span class="n">mods</span> <span class="o">=</span> <span class="s2">&#34;SUPER&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">			<span class="n">action</span> <span class="o">=</span> <span class="n">wezterm.action</span><span class="p">.</span><span class="n">DisableDefaultAssignment</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="p">},</span>
</span></span><span class="line"><span class="cl">	<span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Where this is pretty basic, I set my colour scheme to catppuccin mocha which I use across all of my tools/apps for a
consistent look and feel. Then I set the default shell to fish and my default font to monolisa my current favourite
font (even though you have to pay for it, I just really like how it looks).</p>
<p>Then I disable the <code>super + t</code> key so it doesn&rsquo;t create new tabs. As, I will leverage a multiplexer like zellij or tmux.
Which we will go over in a future post.</p>
<h2 id="more-ramblings">More Ramblings</h2>
<p>As I said, the terminal is probably the least important part of this workflow, as they  all do the same thing. So
pick your favourite and use that. As you can see for my current config, there is nothing special or really that different.
Currently, it is pretty simple. I may later combine this file, when I want to dynamically set the <code>default_prog</code> and
<code>font</code>.</p>
<p>One final thing, for the moment wezterm is crashing on NixOS, there is a fix in place, but there hasn&rsquo;t been a
new release (tag), so I have set up using the git repo to build the latest version of wezterm, so I can use it, until
a new version is released in nixpkgs. At the time of writing, the last release for wezterm was <code>20230712-072601-f4abf8fd</code>,
which was released on July 12th, 2023. You can find how I packaged it
<a href="https://gitlab.com/hmajid2301/dotfiles/-/blob/b9f1454e8bc07d4af7192c5a48a53a765d586646/pkgs/wezterm-nightly/default.nix">here</a>.</p>
<p>Which I nicked from someone else and changed it a bit to make it work with a new release of wezterm. But I cannot
where I found the original.</p>
<p>But that&rsquo;s about it, to be honest!</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://wezfurlong.org/wezterm/features.html">https://wezfurlong.org/wezterm/features.html</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: Show to Use the Media Keys on a ZSA Keyboard With Hyprland</title>
      <link>https://haseebmajid.dev/posts/2024-01-01-how-to-use-the-media-keys-on-a-zsa-keyboard-with-hyprland/</link>
      <pubDate>Mon, 01 Jan 2024 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2024-01-01-how-to-use-the-media-keys-on-a-zsa-keyboard-with-hyprland/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: Show to Use the Media Keys on a ZSA Keyboard With Hyprland&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Recently, I started using a ZSA Voyager split keyboard, moving to this keyboard has some advantages but the last thing
I felt I was missing from my old keyboard (which has a volume knob) was being able to control the volume. So I set up
the key maps in their software (Oryx) however, I noticed that binding were not working.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: Show to Use the Media Keys on a ZSA Keyboard With Hyprland</strong></p>
<p>Recently, I started using a ZSA Voyager split keyboard, moving to this keyboard has some advantages but the last thing
I felt I was missing from my old keyboard (which has a volume knob) was being able to control the volume. So I set up
the key maps in their software (Oryx) however, I noticed that binding were not working.</p>
<p>Where I had something like this:</p>
<pre tabindex="0"><code>bind=,XF86AudioRaiseVolume,exec, volume --inc
bind=,XF86AudioLowerVolume,exec, volume --dec
bind=,XF86AudioMute,exec, volume --toggle
bind=,XF86AudioMicMute,exec, volume --toggle-mic
</code></pre><p>This worked with my old keyboard just fine, however with my new keyboard, I realised I was pressing a key to change
the layers to access the media keys. I press what would normally be a Shift key to change to layer 3, where my media
keys are. As I don&rsquo;t have enough keys to do that all in one layer, like I could do on my other keyboard.</p>
<p></p>
<p>I think since I was holding down a key at the same time as pressing the media keys, I ended up using <code>bindi</code>
to fix my issue.</p>
<blockquote>
<p>i -&gt; ignore mods, will ignore modifiers - <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</p>
</blockquote>
<p>So now my config looks like:</p>
<pre tabindex="0"><code>bindi=,XF86AudioRaiseVolume,exec, volume --inc
bindi=,XF86AudioLowerVolume,exec, volume --dec
bindi=,XF86AudioMute,exec, volume --toggle
bindi=,XF86AudioMicMute,exec, volume --toggle-mic
</code></pre><p>That&rsquo;s it!</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://wiki.hyprland.org/hyprland-wiki/pages/Configuring/Binds/#bind-flags">https://wiki.hyprland.org/hyprland-wiki/pages/Configuring/Binds/#bind-flags</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>2023 In Summary, My Year of The Terminal</title>
      <link>https://haseebmajid.dev/posts/2023-12-31-2023-in-summary-my-year-of-the-terminal/</link>
      <pubDate>Sun, 31 Dec 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-12-31-2023-in-summary-my-year-of-the-terminal/</guid>
      <description>&lt;p&gt;For once, I am actually doing a year in review, to go over some of the things I&amp;rsquo;ve learnt over the last year.
Part of the reason is this has been probably my most transformative year since I have been a software engineer.
I have become far more terminal development driven, if that is a phrase one can use. What I mean is I changed my
development workflow to leverage being in the terminal more. I feel far more productive and efficient than I was at
the beginning of the year, for what it&amp;rsquo;s worth&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>For once, I am actually doing a year in review, to go over some of the things I&rsquo;ve learnt over the last year.
Part of the reason is this has been probably my most transformative year since I have been a software engineer.
I have become far more terminal development driven, if that is a phrase one can use. What I mean is I changed my
development workflow to leverage being in the terminal more. I feel far more productive and efficient than I was at
the beginning of the year, for what it&rsquo;s worth</p>
<p>The following sections will break down what I learnt, why I found it useful. The sections are ordered roughly in
the order I learnt the tool/thing throughout the year.</p>
<h2 id="neovim">Neovim</h2>
<p>The first big change I made was in April. I moved away from using VS Code to Neovim, mostly to flex on other developers
(joking of course). I had a colleague who started to have a look at Neovim, and I was intrigued at the things you can
now do in Neovim. It looked like it had all the main features I was using in VS Code.</p>
<p>I originally started using <a href="https://astronvim.com/">Astronvim</a>, but when I started adding my own plugins I found I broke it effortlessly, probably
a layer 8 issue. I then moved over to <a href="https://www.lazyvim.org/">Folke&rsquo;s LazyVim</a>, which had much better defaults and plugins for what I wanted.</p>
<p>Furthermore, I also took this as an opportunity to learn more vim binding and to become more efficient in vim. As I had got a bit
complacent with my current knowledge of vim bindings and shortcuts to move around and edit code.</p>
<p>Eventually, I ended up creating my own configuration, which most people recommend trying to do. I ended up using NixVim
to do this (more on NixOS later). But it was very useful to be able to see what Neovim could do, before I started
on my own config.</p>
<p>Since VS Code and my Neovim config both use the same LSP behind the scenes gopls, for Golang development I wasn&rsquo;t really
missing out on any features that VS Code offered over Neovim. Except perhaps remote sharing.</p>
<p>So why use Neovim:</p>
<ul>
<li>As most things with me and using Linux, I like to be able to customise and personalise things for myself</li>
<li>Requires fewer resources to run compared with my old editor</li>
<li>Runs in the terminal so can use it with tmux</li>
<li>Forced me to learn my tools a lot better</li>
</ul>
<p>By staying in the terminal, it allowed me to use tmux with Neovim, and manage my different projects much easier. It also
made it a lot more efficient to jump between different projects. Which is very useful when working with microservices.</p>
<p>If you want to use Neovim, I recommend doing the following:</p>
<ul>
<li>learn vim bindings, use a plugin in your current editor</li>
<li>use a distribution like LazyVim</li>
<li>write your own config</li>
<li>alongside this, look at other peoples&rsquo; workflow and see what you like, i.e. using tmux.</li>
</ul>
<p>Some of my favourite plugins:</p>
<ul>
<li>telescope of course</li>
<li>dap for debugging</li>
<li>neotest for testing</li>
<li>oil, for managing folders and files</li>
<li>codeium for AI completions</li>
</ul>
<h3 id="tmux">Tmux</h3>
<p>One of the main reasons I ended up moving to Neovim was that I was able to stay in the terminal more. I found a really
good workflow with Tmux. Where each folder/repository, would be a separate session in tmux. I would then use a the <a href="https://github.com/joshmedeski/t-smart-tmux-session-manager">
t smart session manager</a>, with the tmux resurrect and continuum plugins to help manage my sessions I have.</p>
<p>Tmux is a multiplexer, with a client server architecture, which means I can kill the terminal and the session still is
alive on the server. It also means I could open terminal windows effortlessly. Creating splits with ease. It also
stopped me from having a hundred VS Code instances open and trying to work out which one is the one I need.</p>
<p>For me, whilst Neovim was really cool to learn, combining it with tmux was a real game changer for my workflow. It just
made me feel so much more efficient, jumping between sessions, without needing a mouse being able to be more keyboard
driven.</p>
<h2 id="touch-typing">Touch Typing</h2>
<p>To become a more efficient developer, I also started to learn how to touch type properly, using all 5 of my fingers vs
previously relying mostly on two fingers. Even though I could type at a decent speed of 80 WPM, I would make plenty of mistakes.
Eventually, it would be much better for my wrists and fingers to touch type properly. Using several tools such as
typing club and monkey type. I was able to get back to my normal typing speed in a few months, which fewer mistakes.</p>
<p>Especially as I was now trying to use my vim bindings, and expanding my knowledge of vim shortcuts, I didn&rsquo;t want to have
to think where certain letters were or go and redo a shortcut because I made a mistake.</p>
<p>If you don&rsquo;t know how to touch type, I would recommend spending the effort, Especially if you look down when you type
it&rsquo;s better for you ergonomically and will be better for you eventually, I think.</p>
<h2 id="nixos">NixOS</h2>
<p>One of the other significant changes was in May I moved from Arch (I used arch btw), to shock surprise NixOS. I divided straight
into it. Which probably wasn&rsquo;t the cleverest thing I&rsquo;ve ever done. As there was a steep learning curve. I&rsquo;ve written
some previous articles which go more into detail about NixOS and how I set it up.</p>
<p><img
        loading="lazy"
        src="/posts/2023-12-31-2023-in-summary-my-year-of-the-terminal/images/btw.jpg"
        type=""
        alt="NixOS"
        
      /></p>
<p>The best thing about Nix was I can use declaratively set up my config, which makes it easier to keep multiple machines
in sync. I also think alongside home-manager it makes managing my user, such as installing software and config
for the apps. So even when I use another OS, I can still use nix and share config between multiple machines.</p>
<p>At times, it does feel like I am fighting the OS, especially when the package I need is not in nixpkgs, but overall, I am
happy to be able to keep all my config in code. Sync multiple devices. Also being able to revert to older
generations, making it harder to break my system.</p>
<p>I think where nix is powerful is its development environments you can create using nix flakes. Which solves a
lot of issues I had with dev containers. Making sure people have the same versions of the tools they require for a project.</p>
<p>So why use NixOS:</p>
<ul>
<li>declarative: put your config in code</li>
<li>rollbacks: easy to rollback changes</li>
<li>multiple versions of the same tool, installed</li>
</ul>
<p>If you want to use NixOS, I recommend doing the following:</p>
<ul>
<li>Try nix package manager on your current setup</li>
<li>Try NixOS in a VM</li>
<li>Move to NixOS, installing via an ISO</li>
<li>Look at nix flakes, for even more reproducibility</li>
</ul>
<p>You can read more about my basic <a href="https://haseebmajid.dev/posts/2023-10-24-part-2-how-to-setup-nixos-as-part-of-your-development-workflow/">setup here</a></p>
<h2 id="hyprland">Hyprland</h2>
<p>Alongside moving to NixOS, soon after I moved away from Gnome to Hyprland I wanted to have another go at a tiling window manager.
I kept seeing people using Hyprland, I tried a tiling manager QTile but found it a pain to
set everything up myself, that of a normal desktop environment.</p>
<p>Hyprland is nice because Wayland support, you might be thinking, why not use sway. I ended up using Hyprland because
the desktop sharing support was better with its own desktop portals fork. Allowing me to share a specific window
rather than my entire screen. This made it more practical to use at work. It also has a few nice features that people like
when ricing, like rounded corners, gaps and also some slick animations.</p>
<p>Other than that, a tiling manager allows you  to customise your setup to almost exactly whatever you want, as you
have to set up things like a notification daemon, app launcher and a status bar if you want one. It also allows us to
be more keyboard focused.</p>
<p>I think Hyprland has helped me use the keyboard more, relying on the mouse less. Allowing to be organised more
keeping different apps in different workspaces. Such as one for my main, another for the browser and for my to-dos etc.</p>
<p>You can read more about my <a href="https://haseebmajid.dev/posts/2023-11-15-part-3-hyprland-as-part-of-your-development-workflow/">Hyprland setup here</a></p>
<h2 id="split-keyboard">Split Keyboard</h2>
<p>Another one I copied from a colleague at work, he used a split keyboard so very late into the year, around December
I decided to take the plunge and buy a ZSA Voyager. Split keyboards, also sometimes called ergonomic keyboards, have
several benefits, here is a <a href="https://www.youtube.com/watch?v=76eALNFp3kk">link</a> to the video that finally convinced me.</p>
<p>The main advantages are because its split it opens up your chest, the keys are column staggered and so your fingers
just need to go up and down rather to the side. They are not cheap, however, and are quite an investment. So this
It would be something I upgrade after having most of the rest of your setup, such as a mouse, monitors etc. As a stop
gap mechanical keyboards are plenty affordable these days. You can get some fantastic ones cheapish, especially if
you can get it during a sale.</p>
<h2 id="desktop-vs-laptop">Desktop vs Laptop</h2>
<p>I bought a framework laptop, with an eGPU and then removed my desktop, and have had two stints trying to just live using
my laptop. Which is cool as it&rsquo;s more portable and when it&rsquo;s on my desk I can make more of a workstation. Close the lid
and connect it to all my peripherals, giving me a desktop like experience. Two monitors, keyboard, mouse, webcam and mic
etc.</p>
<p>Especially with the AMD motherboard, I am even able to play some games with minimal lag on 4k monitors without an
eGPU, such as gw2 and master duel (I know not very extensive games but still very cool).</p>
<p>I tried it earlier in the year and gave up on it and moved back to my desktop as it made gaming easier. But I am going
to give it another crack, especially with an AMD board, making Guild Wars 2 playable.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Use Default Values in docker-compose.yml</title>
      <link>https://haseebmajid.dev/posts/2023-12-30-til-how-to-use-default-values-in-docker-compose-yml/</link>
      <pubDate>Sat, 30 Dec 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-12-30-til-how-to-use-default-values-in-docker-compose-yml/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Use Default Values in docker-compose.yml&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Sometimes we want to use env variables in our docker-compose files like so:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;services&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;client&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/nginx&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;ports&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;- &lt;span class=&#34;m&#34;&gt;8000&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;80&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here we are going to use the GitLab CI dependency proxy to pull our Nginx image, so we can speed up our pipelines but
also avoid being rate limited by docker hub. However, when running this locally, we will need to make sure the
&lt;code&gt;CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX&lt;/code&gt; env variable is set. Which just adds a bit more work, instead we can leverage
some of the special syntax docker-compose provides &lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;, which I think it inherits from bash.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Use Default Values in docker-compose.yml</strong></p>
<p>Sometimes we want to use env variables in our docker-compose files like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">client</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/nginx</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="m">8000</span><span class="p">:</span><span class="m">80</span><span class="w">
</span></span></span></code></pre></div><p>Here we are going to use the GitLab CI dependency proxy to pull our Nginx image, so we can speed up our pipelines but
also avoid being rate limited by docker hub. However, when running this locally, we will need to make sure the
<code>CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX</code> env variable is set. Which just adds a bit more work, instead we can leverage
some of the special syntax docker-compose provides <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>, which I think it inherits from bash.</p>
<p>Where we can use interpolation to set default values if the env variable is not set:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">client</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX:-docker.io}/nginx</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>- <span class="m">8000</span><span class="p">:</span><span class="m">80</span><span class="w">
</span></span></span></code></pre></div><p>In this case, <code>:-</code> will use docker.io if the env variable is not set or is empty. There are several other variations
you can find in the footnote below.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://docs.docker.com/compose/environment-variables/env-file/">https://docs.docker.com/compose/environment-variables/env-file/</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Use Open Buffers When Debugging With Neovim&#39;s DAP Plugin</title>
      <link>https://haseebmajid.dev/posts/2023-12-24-til-how-to-use-open-buffers-when-debugging-with-neovim-dap-plugin/</link>
      <pubDate>Sun, 24 Dec 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-12-24-til-how-to-use-open-buffers-when-debugging-with-neovim-dap-plugin/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Use Open Buffers When Debugging With Neovim&amp;rsquo;s DAP Plugin&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I had an issue that whilst debugging my Golang tests in Neovim, the buffer I was focusing on would always change to
the breakpoint. Even when the buffer was open and visible already, say I had the code with the breakpoint on the left
buffer and the tests I was starting the debugger from on the right buffer.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Use Open Buffers When Debugging With Neovim&rsquo;s DAP Plugin</strong></p>
<p>I had an issue that whilst debugging my Golang tests in Neovim, the buffer I was focusing on would always change to
the breakpoint. Even when the buffer was open and visible already, say I had the code with the breakpoint on the left
buffer and the tests I was starting the debugger from on the right buffer.</p>
<p>You can see the example belows:</p>
<p><img
        loading="lazy"
        src="/posts/2023-12-24-til-how-to-use-open-buffers-when-debugging-with-neovim-dap-plugin/images/example.png"
        type=""
        alt="Example Setup"
        
      /></p>
<p>When I start the DAP debugger on the right hand side, by using it to debug the nearest tests, the buffer will change
to the breakpointed line. However I want it to use the open buffer on the left because it is already open.</p>
<p>To do this we need to update our Neovim options: <code>vim.o.switchbuf = &quot;useopen,uselast&quot;</code>. This will now behave as we
want. Note <code>uselast</code>, here is the default value for this option. You can read more about by typing <code>:help 'switchbuf</code>.</p>
<p>That&rsquo;s it! We can now debugger our code and keep the layout we have.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Use Tailscale to Connect to K3S PI Cluster</title>
      <link>https://haseebmajid.dev/posts/2023-12-20-til-how-to-use-tailscale-to-connect-to-k3s-pi-cluster/</link>
      <pubDate>Wed, 20 Dec 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-12-20-til-how-to-use-tailscale-to-connect-to-k3s-pi-cluster/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Use Tailscale to Connect to K3S PI Cluster&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;So what do we do if want to connect to our K3S cluster running our RPIs, but we are not on the same network/at home.
Well we can look to use a VPN, in this article we will be using &lt;a href=&#34;https://tailscale.com/&#34;&gt;tailscale&lt;/a&gt;. It is super easy
to setup on NixOS, and we need very little config for Tailscale. It also has a generous free tier, which will be
more than enough for our home lab use case.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Use Tailscale to Connect to K3S PI Cluster</strong></p>
<p>So what do we do if want to connect to our K3S cluster running our RPIs, but we are not on the same network/at home.
Well we can look to use a VPN, in this article we will be using <a href="https://tailscale.com/">tailscale</a>. It is super easy
to setup on NixOS, and we need very little config for Tailscale. It also has a generous free tier, which will be
more than enough for our home lab use case.</p>
<p>So essentially, what we will need to do is have a Tailscale service always running on each of PI hosts, though we
could have it running on our main control node only (called strawberry from me, see previous articles in this series
for more context). Once we start the service we will need to auth with Tailscale the first time, manually though.</p>
<p>The device we want to connect to our cluster from will also need to be running the Tailscale VPN, but we only need it
running when we want to access our cluster.</p>
<p>All we need to do is add a line to our <code>common.nix</code>, this line <code>services.tailscale.enable = true;</code>. We can then
deploy this using Colmena like <code>colmena apply switch --build-on-target</code>. This will deploy tailscale onto each our PI
cluster nodes.</p>
<p>Then to authenticate with Tailscale we can ssh to our node and run the following <code>sudo tailscale up</code>. We can then auth
in the browser by logging in. We can then also see the device on the Tailscale website.</p>
<p><img
        loading="lazy"
        src="/posts/2023-12-20-til-how-to-use-tailscale-to-connect-to-k3s-pi-cluster/images/tailscale.png"
        type=""
        alt="Tailscale GUI"
        
      /></p>
<p>Then where before I might use <code>strawberry.local</code> to connect now I can use <code>strawberry</code> when I am on the Tailscale VPN.
Note if you want self host rather than using Tailscale, there is an open source project called
<a href="https://github.com/juanfont/headscale">headscale</a>.</p>
<p>That&rsquo;s it! We deployed Tailscale and can connect to our PI K3S cluster, from anywhere.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Deploy K3s With Colmena on Pi Cluster</title>
      <link>https://haseebmajid.dev/posts/2023-12-16-how-to-deploy-k3s-with-colmena-on-pi-cluster/</link>
      <pubDate>Sat, 16 Dec 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-12-16-how-to-deploy-k3s-with-colmena-on-pi-cluster/</guid>
      <description>&lt;p&gt;In this post, we will go over how we can deploy K3S on our PI cluster we have set up. Which is running NixOS,
and we can also pass secrets using sops nix based on the previous parts of this series.&lt;/p&gt;
&lt;p&gt;Some of you maybe wondering what is &lt;a href=&#34;https://k3s.io/&#34;&gt;K3S&lt;/a&gt;, it is a Kubernetes distribution which is tiny i.e.
the binary is only 50 MB. It also has fewer dependencies. Make it perfect our PI cluster and home lab and IoT apps.
I am still going to work out how to manage the Kubernetes cluster itself, perhaps I will use Pulumi you could also
terraform if you wanted a reproducible PI cluster.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this post, we will go over how we can deploy K3S on our PI cluster we have set up. Which is running NixOS,
and we can also pass secrets using sops nix based on the previous parts of this series.</p>
<p>Some of you maybe wondering what is <a href="https://k3s.io/">K3S</a>, it is a Kubernetes distribution which is tiny i.e.
the binary is only 50 MB. It also has fewer dependencies. Make it perfect our PI cluster and home lab and IoT apps.
I am still going to work out how to manage the Kubernetes cluster itself, perhaps I will use Pulumi you could also
terraform if you wanted a reproducible PI cluster.</p>
<p>In our <code>common.nix</code>, let&rsquo;s enable the k3s service which will start k3s for us, one of the nice things about Nix. We
will also add the token that the K3S nodes will need to communicate with each other:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">services</span><span class="o">.</span><span class="n">k3s</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">sops</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">k3s_token</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">sopsFile</span> <span class="o">=</span> <span class="sr">./secrets.yaml</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">services</span><span class="o">.</span><span class="n">k3s</span><span class="o">.</span><span class="n">tokenFile</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">sops</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">k3s_token</span><span class="o">.</span><span class="n">path</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This needs to be secret, so we will use the sops-nix. In my case, I deployed the main node and then took the key
from the node itself and shared it with the agents, so they can join the cluster. However, when we start the main node
we can also pass it a token we specify. Sops saves the token to a file; hence we use <code>tokenFile</code> here, as we cannot
actually access the value in our nix config.</p>
<p>We may also want to update our firewall, and open ports so that the nodes in the cluster can communicate each other
from the hosts.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">networking</span><span class="o">.</span><span class="n">firewall</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">allowedTCPPorts</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="mi">22</span>
</span></span><span class="line"><span class="cl">      <span class="mi">6443</span>
</span></span><span class="line"><span class="cl">      <span class="mi">6444</span>
</span></span><span class="line"><span class="cl">      <span class="mi">9000</span>
</span></span><span class="line"><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>We don&rsquo;t need to do much else for the main node, but for each agent node we need to tell it is that it should act
as an agent, and which hostname to connect to join the nodes. Which we can do something like this <code>mango.nix</code>;</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">services</span><span class="o">.</span><span class="n">k3s</span><span class="o">.</span><span class="n">role</span> <span class="o">=</span> <span class="s2">&#34;agent&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">services</span><span class="o">.</span><span class="n">k3s</span><span class="o">.</span><span class="n">serverAddr</span> <span class="o">=</span> <span class="s2">&#34;https://strawberry.local:6443&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Where they are all running in the same local network; hence they can connect using the hostname and <code>.local</code>. This was
avahi service we set up in a previous post.</p>
<p>So in each 3 of my agent nodes this config is copies, so these nodes act as agents and connect to the k8s cluster
correctly. We can then deploy the app as we normally would using Colmena, <code>colmena apply switch --build-on-target</code>.</p>
<p>Then we can follow this <a href="https://docs.k3s.io/cluster-access">tutorial</a> for cluster access using <code>kubectl</code>. We can
then check all the nodes in the cluster by doing:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">kubectl get nodes
</span></span><span class="line"><span class="cl">NAME         STATUS   ROLES                  AGE   VERSION
</span></span><span class="line"><span class="cl">strawberry   Ready    control-plane,master   41d   v1.27.6+k3s1
</span></span><span class="line"><span class="cl">orange       Ready    &lt;none&gt;                 34d   v1.27.6+k3s1
</span></span><span class="line"><span class="cl">mango        Ready    &lt;none&gt;                 32d   v1.27.6+k3s1
</span></span><span class="line"><span class="cl">guava        Ready    &lt;none&gt;                 35d   v1.27.6+k3s1
</span></span></code></pre></div><p>That&rsquo;s it! We successfully deployed k3s to our PI cluster using Colmena and even sops-nix from our previous posts.
In the next post, we will look at how we can add Tailscale to be able to securely connect to our cluster from anywhere.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Fix a NTFS Drive on NixOS</title>
      <link>https://haseebmajid.dev/posts/2023-12-06-til-how-to-ntfs-drive-on-nixos/</link>
      <pubDate>Wed, 06 Dec 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-12-06-til-how-to-ntfs-drive-on-nixos/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to NTFS Drive on NixOS&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Recently, I was trying to open an NTFS drive on my NixOS machine; however, the drive was corrupted. So I did the
following to fix the drive.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;nix-shell -p ntfs3g
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ntfsfix /dev/sda1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Where &lt;code&gt;/dev/sda1&lt;/code&gt; is the broken drive. This was enough for me to be able to mount the drive and access the files on it.
I didn&amp;rsquo;t need to fix it on a Window machine.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to NTFS Drive on NixOS</strong></p>
<p>Recently, I was trying to open an NTFS drive on my NixOS machine; however, the drive was corrupted. So I did the
following to fix the drive.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">nix-shell -p ntfs3g
</span></span><span class="line"><span class="cl">ntfsfix /dev/sda1
</span></span></code></pre></div><p>Where <code>/dev/sda1</code> is the broken drive. This was enough for me to be able to mount the drive and access the files on it.
I didn&rsquo;t need to fix it on a Window machine.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Append to a Config Option String in Nix</title>
      <link>https://haseebmajid.dev/posts/2023-12-01-til-how-to-append-to-a-config-option-string-in-nix/</link>
      <pubDate>Fri, 01 Dec 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-12-01-til-how-to-append-to-a-config-option-string-in-nix/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Append to a Config Option String in Nix&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Recently on my laptop I had to add some extra config settings to my Hyprland config. Rather than polluting my
&lt;code&gt;hyprland.nix&lt;/code&gt; file with if, else depending on the host. I wanted to add the extra config in the &lt;code&gt;home.nix&lt;/code&gt; of the
host. So it&amp;rsquo;s contained within that host. Where my &lt;code&gt;home.nix&lt;/code&gt; is the entry point for my
home-manager config for that host.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Append to a Config Option String in Nix</strong></p>
<p>Recently on my laptop I had to add some extra config settings to my Hyprland config. Rather than polluting my
<code>hyprland.nix</code> file with if, else depending on the host. I wanted to add the extra config in the <code>home.nix</code> of the
host. So it&rsquo;s contained within that host. Where my <code>home.nix</code> is the entry point for my
home-manager config for that host.</p>
<p>My <code>hyprland.nix</code> looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="n">wayland</span><span class="o">.</span><span class="n">windowManager</span><span class="o">.</span><span class="n">hyprland</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">extraConfig</span> <span class="o">=</span> <span class="s1">&#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">     input {
</span></span></span><span class="line"><span class="cl"><span class="s1">        kb_layout = gb
</span></span></span><span class="line"><span class="cl"><span class="s1">        touchpad {
</span></span></span><span class="line"><span class="cl"><span class="s1">            disable_while_typing=false
</span></span></span><span class="line"><span class="cl"><span class="s1">        }
</span></span></span><span class="line"><span class="cl"><span class="s1">     }
</span></span></span><span class="line"><span class="cl"><span class="s1">  &#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>I want to add some extra config to the <code>extraConfig</code> option/attribute of this attribute set.
Then in my <code>home.nix</code> I added the following:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="n">wayland</span><span class="o">.</span><span class="n">windowManager</span><span class="o">.</span><span class="n">hyprland</span><span class="o">.</span><span class="n">extraConfig</span> <span class="o">=</span> <span class="n">lib</span><span class="o">.</span><span class="n">mkAfter</span> <span class="s1">&#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">  exec-once = /usr/libexec/geoclue-2.0/demos/agent
</span></span></span><span class="line"><span class="cl"><span class="s1">  exec-once = warp-taskbar
</span></span></span><span class="line"><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="cl"><span class="s1">  bind=,XF86Launch5,exec,/usr/local/bin/swaylock -S
</span></span></span><span class="line"><span class="cl"><span class="s1">  bind=,XF86Launch4,exec,/usr/local/bin/swaylock -S
</span></span></span><span class="line"><span class="cl"><span class="s1">  bind=SUPER,backspace,exec,/usr/local/bin/swaylock -S
</span></span></span><span class="line"><span class="cl"><span class="s1">&#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>In this case we want to start some processes on this host, like showing warp in my taskbar.
The key line here being <code>lib.mkAfter</code>. Which will append the content after the existing config options we defined in <code>hyprland.nix</code></p>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Use Sops Nix With Colmena</title>
      <link>https://haseebmajid.dev/posts/2023-11-30-til-how-to-use-sops-nix-with-colmena/</link>
      <pubDate>Thu, 30 Nov 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-11-30-til-how-to-use-sops-nix-with-colmena/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Use Sops Nix With Colmena&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If we are using colmena, how can we set it up when we deploy a secret, for example when deploying k3s the token?
i.e. &lt;code&gt;services.k3s.tokenFile = &amp;quot;/my.token&amp;quot;;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So to do this first, I will assume you already have a colmena config and sops-nix setup in your config.
First, let&amp;rsquo;s set up our hosts, in this case RPIs which already come &lt;code&gt;/etc/ssh/ssh_host_ed25519_key&lt;/code&gt; ssh key we can turn
to an age key, i.e. in our &lt;code&gt;.sops.yaml&lt;/code&gt;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Use Sops Nix With Colmena</strong></p>
<p>If we are using colmena, how can we set it up when we deploy a secret, for example when deploying k3s the token?
i.e. <code>services.k3s.tokenFile = &quot;/my.token&quot;;</code>.</p>
<p>So to do this first, I will assume you already have a colmena config and sops-nix setup in your config.
First, let&rsquo;s set up our hosts, in this case RPIs which already come <code>/etc/ssh/ssh_host_ed25519_key</code> ssh key we can turn
to an age key, i.e. in our <code>.sops.yaml</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">keys</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="cp">&amp;users:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="cp">&amp;haseeb</span><span class="w"> </span><span class="l">F04F743A24CD81B628A20667CD20E7373D83B71C</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="cp">&amp;hosts:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="cp">&amp;strawberry</span><span class="w"> </span><span class="l">age1qng4kav7deqtjmxeqz2vnyxywaqplf8k2lu3q347r2rz4zxdsynq0sf4um</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="cp">&amp;orange</span><span class="w"> </span><span class="l">age187eesfqwv04gpd2dnfwsjgleevr57v6xvrwujjy8ehhf0ehl338qdnlqlf</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="cp">&amp;guava</span><span class="w"> </span><span class="l">age10qsd50v2qmvn4vy4l8cjxvjxjuvedkxjc0a72ap9laap9mz6rctqmp3efl</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="cp">&amp;mango</span><span class="w"> </span><span class="l">age16tskx6gle6v4v0hzhm5fvj0yd29mmn0s47d8q0h3tgcj9wej53uquv98cn</span><span class="w">
</span></span></span></code></pre></div><p>To get this file, we need to log in to our rpi host and run
<code>nix-shell -p ssh-to-age --run 'cat /etc/ssh/ssh_host_ed25519_key.pub | ssh-to-age'</code>. Then we add our secrets file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">creation_rules</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">path_regex</span><span class="p">:</span><span class="w"> </span><span class="l">hosts/rpis/secrets.ya?ml$</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">key_groups</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">age</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="cp">*strawberry</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="cp">*orange</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="cp">*guava</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="cp">*mango</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">pgp</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="cp">*haseeb</span><span class="w">
</span></span></span></code></pre></div><p>Then we can create our actual secrets file running <code>sops hosts/rpis/secrets.yaml</code>. Now we can reference
these secrets in our colmena config. Let&rsquo;s add sops to our common config:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">defaults</span> <span class="o">=</span> <span class="p">{</span> <span class="n">pkgs</span><span class="o">,</span> <span class="o">...</span> <span class="p">}:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">imports</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="n">inputs</span><span class="o">.</span><span class="n">hardware</span><span class="o">.</span><span class="n">nixosModules</span><span class="o">.</span><span class="n">raspberry-pi-4</span>
</span></span><span class="line hl"><span class="cl">      <span class="n">inputs</span><span class="o">.</span><span class="n">sops-nix</span><span class="o">.</span><span class="n">nixosModules</span><span class="o">.</span><span class="n">sops</span>
</span></span><span class="line"><span class="cl">      <span class="sr">./rpis/common.nix</span>
</span></span><span class="line"><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>For example, if we take look at <code>common.nix</code> we can use sops like we normally would:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="n">sops</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">k3s_token</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">sopsFile</span> <span class="o">=</span> <span class="sr">./secrets.yaml</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">services</span><span class="o">.</span><span class="n">k3s</span><span class="o">.</span><span class="n">tokenFile</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">sops</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">k3s_token</span><span class="o">.</span><span class="n">path</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">sops</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">age</span><span class="o">.</span><span class="n">sshKeyPaths</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;/etc/ssh/ssh_host_ed25519_key&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Then, we can run <code>colmena switch</code> like we usually would. Then the secret is made available at <code>/run/secrets/k3s_token</code>
on the rpis like it normally would be.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>How I Manage My Raspberry Pi Cluster Using Colmena</title>
      <link>https://haseebmajid.dev/posts/2023-11-28-how-to-manage-my-raspberry-pi-cluster-using-colmena/</link>
      <pubDate>Tue, 28 Nov 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-11-28-how-to-manage-my-raspberry-pi-cluster-using-colmena/</guid>
      <description>&lt;p&gt;So in the previous article I showed you how I had set up my 4 RPI (Raspberry Pi) cluster and put NixOS on the machines.
They are now connectable over SSH using just their hostnames, i.e. &lt;code&gt;ssh strawberry@strawberry.local&lt;/code&gt;. Initially
we deployed NixOS and a basic configuration to each of the RPIs manually.&lt;/p&gt;
&lt;p&gt;We want to automate this process rather than deploying to each machine manually. I looked at
&lt;a href=&#34;https://github.com/rapenne-s/bento/&#34;&gt;bento&lt;/a&gt;, but couldn&amp;rsquo;t quite work out how to make it work for my use case.
Then I found &lt;a href=&#34;https://github.com/zhaofengli/colmena&#34;&gt;colmena&lt;/a&gt;, which worked (is working) to do what I needed.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>So in the previous article I showed you how I had set up my 4 RPI (Raspberry Pi) cluster and put NixOS on the machines.
They are now connectable over SSH using just their hostnames, i.e. <code>ssh strawberry@strawberry.local</code>. Initially
we deployed NixOS and a basic configuration to each of the RPIs manually.</p>
<p>We want to automate this process rather than deploying to each machine manually. I looked at
<a href="https://github.com/rapenne-s/bento/">bento</a>, but couldn&rsquo;t quite work out how to make it work for my use case.
Then I found <a href="https://github.com/zhaofengli/colmena">colmena</a>, which worked (is working) to do what I needed.</p>
<h2 id="what-does-it-do">What does it do?</h2>
<p>We push changes from our Desktop and deploy to all our rpis at once, without needing to deploy to each manually.
Using one command, <code>colmena apply switch --build-on-target</code> It will deploy our changes to all our RPIs. Where we
specify common config shared by all the machines, but then also specific config for each of our RPIs.</p>
<h2 id="setup">Setup</h2>
<p>Let&rsquo;s have a look at how we can set up Colmena, so, in our current Nix flake config. Add colmena as an input:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">inputs</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">colmena</span><span class="o">.</span><span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;github:zhaofengli/colmena&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Then in the outputs, let use specify a few things:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">outputs</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="n">self</span>
</span></span><span class="line"><span class="cl">    <span class="o">,</span> <span class="n">nixpkgs</span>
</span></span><span class="line"><span class="cl">    <span class="o">,</span> <span class="n">colmena</span>
</span></span><span class="line"><span class="cl">    <span class="o">,</span> <span class="o">...</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="o">@</span> <span class="n">inputs</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">colmena</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">meta</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">nixpkgs</span> <span class="o">=</span> <span class="kn">import</span> <span class="n">nixpkgs</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">system</span> <span class="o">=</span> <span class="s2">&#34;x86_64-linux&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">          <span class="p">};</span>
</span></span><span class="line"><span class="cl">          <span class="n">specialArgs</span> <span class="o">=</span> <span class="n">inputs</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">defaults</span> <span class="o">=</span> <span class="p">{</span> <span class="n">pkgs</span><span class="o">,</span> <span class="o">...</span> <span class="p">}:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">imports</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">            <span class="n">inputs</span><span class="o">.</span><span class="n">hardware</span><span class="o">.</span><span class="n">nixosModules</span><span class="o">.</span><span class="n">raspberry-pi-4</span>
</span></span><span class="line"><span class="cl">            <span class="sr">./hosts/rpis/common.nix</span>
</span></span><span class="line"><span class="cl">          <span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">strawberry</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">imports</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">            <span class="sr">./hosts/rpis/strawberry.nix</span>
</span></span><span class="line"><span class="cl">          <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">          <span class="n">nixpkgs</span><span class="o">.</span><span class="n">system</span> <span class="o">=</span> <span class="s2">&#34;aarch64-linux&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">          <span class="n">deployment</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">buildOnTarget</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">targetHost</span> <span class="o">=</span> <span class="s2">&#34;strawberry&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">targetUser</span> <span class="o">=</span> <span class="s2">&#34;strawberry&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">tags</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;rpi&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">          <span class="p">};</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Breaking this file down, first we create a section for colmena, then we specify some meta information i.e.
which nixpkgs to use. Then one of the cool bits of colmena we can specify some common config between all of our hosts.</p>
<p>Here we are importing sops-nix for secret management and hardware module for Raspberry Pi 4, so we can get various
optimised settings.</p>
<h3 id="commonnix">common.nix</h3>
<p>Our <code>common.nix</code> looks something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span> <span class="n">config</span><span class="o">,</span> <span class="n">pkgs</span><span class="o">,</span> <span class="o">...</span> <span class="p">}:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">boot</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">kernelPackages</span> <span class="o">=</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">linuxKernel</span><span class="o">.</span><span class="n">packages</span><span class="o">.</span><span class="n">linux_rpi4</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">kernelParams</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;cgroup_memory=1&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;cgroup_enable=cpuset&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;cgroup_enable=memory&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">initrd</span><span class="o">.</span><span class="n">availableKernelModules</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;xhci_pci&#34;</span> <span class="s2">&#34;usbhid&#34;</span> <span class="s2">&#34;usb_storage&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="n">loader</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">grub</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">generic-extlinux-compatible</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">fileSystems</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;/&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">device</span> <span class="o">=</span> <span class="s2">&#34;/dev/disk/by-label/NIXOS_SD&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">fsType</span> <span class="o">=</span> <span class="s2">&#34;ext4&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">options</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;noatime&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">networking</span><span class="o">.</span><span class="n">firewall</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">allowedTCPPorts</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="mi">22</span>
</span></span><span class="line"><span class="cl">      <span class="mi">6443</span>
</span></span><span class="line"><span class="cl">      <span class="mi">6444</span>
</span></span><span class="line"><span class="cl">      <span class="mi">9000</span>
</span></span><span class="line"><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">programs</span><span class="o">.</span><span class="n">fish</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">users</span><span class="o">.</span><span class="n">users</span><span class="o">.</span><span class="n">root</span><span class="o">.</span><span class="n">hashedPassword</span> <span class="o">=</span> <span class="s2">&#34;!&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">environment</span><span class="o">.</span><span class="n">systemPackages</span> <span class="o">=</span> <span class="k">with</span> <span class="n">pkgs</span><span class="p">;</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="n">git</span>
</span></span><span class="line"><span class="cl">    <span class="n">vim</span>
</span></span><span class="line"><span class="cl">    <span class="n">wget</span>
</span></span><span class="line"><span class="cl">    <span class="n">curl</span>
</span></span><span class="line"><span class="cl">    <span class="n">gnupg</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">services</span><span class="o">.</span><span class="n">avahi</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">nssmdns</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">publish</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">addresses</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">domain</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">hinfo</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">userServices</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">workstation</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">services</span><span class="o">.</span><span class="n">openssh</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">settings</span><span class="o">.</span><span class="n">PasswordAuthentication</span> <span class="o">=</span> <span class="no">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">settings</span><span class="o">.</span><span class="n">KbdInteractiveAuthentication</span> <span class="o">=</span> <span class="no">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">security</span><span class="o">.</span><span class="n">sudo</span><span class="o">.</span><span class="n">wheelNeedsPassword</span> <span class="o">=</span> <span class="no">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">hardware</span><span class="o">.</span><span class="n">enableRedistributableFirmware</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">system</span><span class="o">.</span><span class="n">stateVersion</span> <span class="o">=</span> <span class="s2">&#34;23.11&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This config will be applied to all of our hosts. It includes things like setting up ssh, installing some packages
and setting up a default shell (fish). Though specifics don&rsquo;t matter much, more just you can put common config
into a nix module.</p>
<h3 id="host-specific-config">Host-Specific Config</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="n">strawberry</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">imports</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="sr">./hosts/rpis/strawberry.nix</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">nixpkgs</span><span class="o">.</span><span class="n">system</span> <span class="o">=</span> <span class="s2">&#34;aarch64-linux&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">deployment</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">buildOnTarget</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">targetHost</span> <span class="o">=</span> <span class="s2">&#34;strawberry.local&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">targetUser</span> <span class="o">=</span> <span class="s2">&#34;strawberry&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">tags</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;rpi&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>We import nix specific config for our host, in this case the host name of the machine is strawberry and gave the same
The name of the colmena &ldquo;resource&rdquo;. Where my <code>strawberry.nix</code> file looks like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span> <span class="n">config</span><span class="o">,</span> <span class="n">pkgs</span><span class="o">,</span> <span class="n">lib</span><span class="o">,</span> <span class="o">...</span> <span class="p">}:</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">let</span>
</span></span><span class="line"><span class="cl">  <span class="n">hostname</span> <span class="o">=</span> <span class="s2">&#34;strawberry&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">in</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">networking</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">hostName</span> <span class="o">=</span> <span class="n">hostname</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">nix</span><span class="o">.</span><span class="n">settings</span><span class="o">.</span><span class="n">trusted-users</span> <span class="o">=</span> <span class="p">[</span> <span class="n">hostname</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">users</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">users</span><span class="o">.</span><span class="s2">&#34;</span><span class="si">${</span><span class="n">hostname</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">isNormalUser</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">shell</span> <span class="o">=</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">fish</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">extraGroups</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;wheel&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">      <span class="n">password</span> <span class="o">=</span> <span class="n">hostname</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">openssh</span><span class="o">.</span><span class="n">authorizedKeys</span><span class="o">.</span><span class="n">keys</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMxe8kDCJa6xcAM9WE8c5amGG+2secXmnof7vlmAq1Da hello@haseebmajid.dev&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Which just includes some specific config like hostname and username, and other ssh keys that can be used to log in to
this user. Again, the specifics don&rsquo;t matter too much, it&rsquo;s more the idea we can have specific config for our strawberry
host.</p>
<p>Then going over the rest of our config:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="n">strawberry</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">imports</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="sr">./hosts/rpis/strawberry.nix</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line hl"><span class="cl">  <span class="n">nixpkgs</span><span class="o">.</span><span class="n">system</span> <span class="o">=</span> <span class="s2">&#34;aarch64-linux&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">deployment</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">buildOnTarget</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">targetHost</span> <span class="o">=</span> <span class="s2">&#34;strawberry.local&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">targetUser</span> <span class="o">=</span> <span class="s2">&#34;strawberry&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">tags</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;rpi&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This specifies things like the host to connect to, i.e. <code>strawberry.local</code> and the user to log in with <code>strawberry</code>.
We can also add <code>tags</code> which we can use during deployment to deploy to machines with specific tags.</p>
<p>Then we simply specify our remaining hosts:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">colmena</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">orange</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">imports</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="sr">./hosts/rpis/orange.nix</span>
</span></span><span class="line"><span class="cl">      <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">nixpkgs</span><span class="o">.</span><span class="n">system</span> <span class="o">=</span> <span class="s2">&#34;aarch64-linux&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">deployment</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">buildOnTarget</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">targetHost</span> <span class="o">=</span> <span class="s2">&#34;orange.local&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">targetUser</span> <span class="o">=</span> <span class="s2">&#34;orange&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">tags</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;rpi&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl"> <span class="c1"># other hosts ...</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><blockquote>
<p>If you set deployment.buildOnTarget = true; for a node, then the actual build process will be initiated on the node itself. Colmena will evaluate the configuration locally before copying the derivations to the target node.</p>
</blockquote>
<p>We want to build the nix config on the pis, rather than on my desktop, as they are different architectures, x86 vs arch.</p>
<h2 id="deploy">Deploy</h2>
<p>Now to deploy to our rpis we can do <code>colmena switch</code>, from my desktop. Where my desktop has connectivity
to all my PIs (running on the same local network). That should be it, as long as we can connect to the PIs from our
machine we run the colmena command on it will deploy the new config.</p>
<p>In the next article, we will look at how we can manage secrets using sops-nix when deploying using colmena.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/dotfiles/-/blob/d8aefb2b2dbd468b221f2b6074994a87761ae981/hosts/self-hosted/colmena.nix">My colmena config</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Set the Path Variable When Using Ubuntu With Nix (Home Manager)</title>
      <link>https://haseebmajid.dev/posts/2023-11-25-til-how-to-set-the-path-variable-when-using-ubuntu-with-nix-home-manager/</link>
      <pubDate>Sat, 25 Nov 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-11-25-til-how-to-set-the-path-variable-when-using-ubuntu-with-nix-home-manager/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Set the Path Variable When Using Ubuntu With Nix (Home Manager)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;As per some of my recent articles, you will be aware I am using Hyprland (tiling manager) on Ubuntu and managing the
config using nix (home-manager). I was having issues where for some reason it wouldn&amp;rsquo;t set the &lt;code&gt;PATH&lt;/code&gt; variable correctly.&lt;/p&gt;
&lt;p&gt;On my NixOS machine, the following would be fine:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;bind&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;,XF86AudioRaiseVolume,exec, volume --inc
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;bind&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;,XF86AudioLowerVolume,exec, volume --dec
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;However, on Ubuntu I needed to provide the full path:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Set the Path Variable When Using Ubuntu With Nix (Home Manager)</strong></p>
<p>As per some of my recent articles, you will be aware I am using Hyprland (tiling manager) on Ubuntu and managing the
config using nix (home-manager). I was having issues where for some reason it wouldn&rsquo;t set the <code>PATH</code> variable correctly.</p>
<p>On my NixOS machine, the following would be fine:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">bind</span><span class="o">=</span>,XF86AudioRaiseVolume,exec, volume --inc
</span></span><span class="line"><span class="cl"><span class="nv">bind</span><span class="o">=</span>,XF86AudioLowerVolume,exec, volume --dec
</span></span></code></pre></div><p>However, on Ubuntu I needed to provide the full path:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">bind</span><span class="o">=</span>,XF86AudioRaiseVolume,exec, <span class="si">${</span><span class="nv">volume</span><span class="si">}</span>/bin/volume --inc
</span></span></code></pre></div><p>So in the Hyprland config it would look like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">bind</span><span class="o">=</span>,XF86AudioRaiseVolume,exec, /nix/store/q1wm84g65smaq4agq7zp40q57x3534ni-volume/bin/volume --inc
</span></span></code></pre></div><p>It was because the session was being started by GDM (gnome), and the <code>PATH</code> variable was not being set properly.
In Wayland GDM picks up environment variables from <code>~/.config/environment.d/envvars.conf</code> <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> so we can add it here.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ bat ~/.config/environment.d/envvars.conf --plain
</span></span><span class="line"><span class="cl"><span class="nv">PATH</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$PATH</span><span class="s2">:/home/haseebmajid/.nix-profile/bin&#34;</span>
</span></span></code></pre></div><p>Replace <code>haseebmajid</code> with your username (or I guess <code>$HOME</code> should work). This means when we run scripts now, it will
source them from this nix-profile folder. Where the binaries get symlinked, including the volume script above. So
now you don&rsquo;t need to specify the full path (though maybe you should 🤷)</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/dotfiles/-/blob/9561a21fed329f25802290621a54588e314af1ee/home-manager/desktops/wms/hyprland.nix">Hyprland Config</a></li>
<li><a href="https://old.reddit.com/r/NixOS/comments/17rilhc/hyprlandsway_needs_full_path_to_scripts_on/">Reddit Thread</a></li>
<li><a href="https://discourse.nixos.org/t/hyprland-sway-need-full-path-to-scripts-on-non-nixos/35233">Discourse Thread</a></li>
</ul>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://wiki.archlinux.org/title/environment_variables#Graphical_environment">https://wiki.archlinux.org/title/environment_variables#Graphical_environment</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Install Hyprland on Ubuntu (22.04)</title>
      <link>https://haseebmajid.dev/posts/2023-11-24-how-to-install-hyprland-on-ubuntu-22-04/</link>
      <pubDate>Fri, 24 Nov 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-11-24-how-to-install-hyprland-on-ubuntu-22-04/</guid>
      <description>&lt;p&gt;As you may know from my previous articles, my work laptop is an Ubuntu (22.04) laptop. However, on the rest of my devices
I use NixOS with Hyprland as my tiling window manager. I wanted to be able to use Hyprland on my laptop as well,
I tried using Sway and, it worked mostly pretty well, but it was yet another to manage and there were some slight
differences. Also, I like the animations on Hyprland 😅.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>As you may know from my previous articles, my work laptop is an Ubuntu (22.04) laptop. However, on the rest of my devices
I use NixOS with Hyprland as my tiling window manager. I wanted to be able to use Hyprland on my laptop as well,
I tried using Sway and, it worked mostly pretty well, but it was yet another to manage and there were some slight
differences. Also, I like the animations on Hyprland 😅.</p>
<p>So I had tried to work out how to install Hyprland on Ubuntu 22.04. I couldn&rsquo;t make it work via Nix/Home-manager.
There is this great gist for how to set it up on
<a href="https://gist.github.com/Vertecedoc4545/3b077301299c20c5b9b4db00f4ca6000">Ubuntu 23.04</a>. However, some of the versions
available to me in the Ubuntu repository didn&rsquo;t match the versions required by Hyprland. Hyprland is bleeding edge
and themselves say they do not officially support Ubuntu.</p>
<p>In that gist above, one of the comments <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> mentions <a href="https://pika-os.com/">Pika OS</a> which builds Debian packages
for Hyprland (and all of it dependencies). So we can add Pika OS as one of our sources and install Hyprland.
To achieve this, I downloaded the <a href="https://ppa.pika-os.com/dists/lunar/pika-sources.deb"><code>dists/lunar/pika-sources.deb</code></a> (Where I assume
Lunar is based on Lunar Lobster, Ubuntu 23.04).</p>
<p>First we can see what it is going to install and where, by doing:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">dpkg -c ./pika-sources.deb
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">drwxr-xr-x root/root         <span class="m">0</span> 2022-10-01 13:50 ./
</span></span><span class="line"><span class="cl">drwxr-xr-x root/root         <span class="m">0</span> 2022-10-01 13:50 ./etc/
</span></span><span class="line"><span class="cl">drwxr-xr-x root/root         <span class="m">0</span> 2022-10-01 13:50 ./etc/init.d/
</span></span><span class="line"><span class="cl">-rwxr-xr-x root/root       <span class="m">247</span> 2022-10-01 13:50 ./etc/init.d/calamares-sources-undo
</span></span><span class="line"><span class="cl">drwxr-xr-x root/root         <span class="m">0</span> 2022-10-01 13:50 ./etc/rc2.d/
</span></span><span class="line"><span class="cl">drwxr-xr-x root/root         <span class="m">0</span> 2022-10-01 13:50 ./etc/rc3.d/
</span></span><span class="line"><span class="cl">drwxr-xr-x root/root         <span class="m">0</span> 2022-10-01 13:50 ./etc/systemd/
</span></span><span class="line"><span class="cl">drwxr-xr-x root/root         <span class="m">0</span> 2022-10-01 13:50 ./etc/systemd/system/
</span></span><span class="line"><span class="cl">-rw-r--r-- root/root       <span class="m">522</span> 2022-10-01 13:50 ./etc/systemd/system/steam-repos-fix.service
</span></span><span class="line"><span class="cl">drwxr-xr-x root/root         <span class="m">0</span> 2022-10-01 13:50 ./usr/
</span></span><span class="line"><span class="cl">drwxr-xr-x root/root         <span class="m">0</span> 2022-10-01 13:50 ./usr/share/
</span></span><span class="line"><span class="cl">drwxr-xr-x root/root         <span class="m">0</span> 2022-10-01 13:50 ./usr/share/apt-pika/
</span></span><span class="line"><span class="cl">drwxr-xr-x root/root         <span class="m">0</span> 2022-10-01 13:50 ./usr/share/apt-pika/apt/
</span></span><span class="line"><span class="cl">drwxr-xr-x root/root         <span class="m">0</span> 2022-10-01 13:50 ./usr/share/apt-pika/apt/apt.conf.d/
</span></span><span class="line"><span class="cl">-rw-r--r-- root/root       <span class="m">146</span> 2022-10-01 13:50 ./usr/share/apt-pika/apt/apt.conf.d/99steam-launcher
</span></span><span class="line"><span class="cl">drwxr-xr-x root/root         <span class="m">0</span> 2022-10-01 13:50 ./usr/share/apt-pika/apt/keyrings/
</span></span><span class="line"><span class="cl">-rw-r--r-- root/root      <span class="m">1744</span> 2022-10-01 13:50 ./usr/share/apt-pika/apt/keyrings/pika-keyring.gpg.key
</span></span><span class="line"><span class="cl">drwxr-xr-x root/root         <span class="m">0</span> 2022-10-01 13:50 ./usr/share/apt-pika/apt/preferences.d/
</span></span><span class="line"><span class="cl">-rw-r--r-- root/root       <span class="m">282</span> 2022-10-01 13:50 ./usr/share/apt-pika/apt/preferences.d/0-pika-radeon-settings
</span></span><span class="line"><span class="cl">-rw-r--r-- root/root        <span class="m">91</span> 2022-10-01 13:50 ./usr/share/apt-pika/apt/preferences.d/1-pika-ubuntu-settings
</span></span><span class="line"><span class="cl">-rw-r--r-- root/root       <span class="m">102</span> 2022-10-01 13:50 ./usr/share/apt-pika/apt/sources.list
</span></span><span class="line"><span class="cl">drwxr-xr-x root/root         <span class="m">0</span> 2022-10-01 13:50 ./usr/share/apt-pika/apt/sources.list.d/
</span></span><span class="line"><span class="cl">-rw-r--r-- root/root       <span class="m">274</span> 2022-10-01 13:50 ./usr/share/apt-pika/apt/sources.list.d/system.sources
</span></span><span class="line"><span class="cl">drwxr-xr-x root/root         <span class="m">0</span> 2022-10-01 13:50 ./usr/share/doc/
</span></span><span class="line"><span class="cl">drwxr-xr-x root/root         <span class="m">0</span> 2022-10-01 13:50 ./usr/share/doc/pika-sources/
</span></span><span class="line"><span class="cl">-rw-r--r-- root/root       <span class="m">617</span> 2022-10-01 13:50 ./usr/share/doc/pika-sources/changelog.Debian.gz
</span></span><span class="line"><span class="cl">drwxr-xr-x root/root         <span class="m">0</span> 2022-10-01 13:50 ./usr/share/dpkg/
</span></span><span class="line"><span class="cl">drwxr-xr-x root/root         <span class="m">0</span> 2022-10-01 13:50 ./usr/share/dpkg/scripts/
</span></span><span class="line"><span class="cl">-rwxr-xr-x root/root       <span class="m">516</span> 2022-10-01 13:50 ./usr/share/dpkg/scripts/steam-launcher.sh
</span></span><span class="line"><span class="cl">-rwxr-xr-x root/root       <span class="m">643</span> 2022-10-01 13:50 ./usr/share/dpkg/scripts/steamdeps
</span></span><span class="line"><span class="cl">lrwxrwxrwx root/root         <span class="m">0</span> 2022-10-01 13:50 ./etc/rc2.d/S01calamares-sources-undo -&gt; ../init.d/calamares-sources-undo
</span></span><span class="line"><span class="cl">lrwxrwxrwx root/root         <span class="m">0</span> 2022-10-01 13:50 ./etc/rc3.d/S01calamares-sources-undo -&gt; ../init.d/calamares-sources-undo
</span></span></code></pre></div><p>The main file being <code>sources.list.d/system.sources</code> which will add the Pika repository a source to our (apt) package
manager. So after we download the pika-source file, we can then do:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Assuming you are in the folder where you downloaded pika-sources.deb</span>
</span></span><span class="line"><span class="cl">dpkg-deb --fsys-tarfile pika-sources.deb <span class="p">|</span> tar xv
</span></span><span class="line"><span class="cl">sudo xcp ./usr/share/apt/source.list.d/system.sources /etc/apt/sources.list.d/system.sources
</span></span><span class="line"><span class="cl">sudo apt update
</span></span><span class="line"><span class="cl">sudo apt install hyprland xdg-desktop-portal-hyprland -y
</span></span></code></pre></div><p>This will install Hyprland and the desktop portal, which will allow us to do things like screen share-specific windows
on apps like Google Meet.</p>
<p>Finally, you can also remove the Pika sources because in my case it wanted to upgrade 1600 packages as it uses
Ubuntu 23.04 as a base. So wants to update numerous packages. To achieve this we do: <code>sudo rm /etc/apt/sources.list.d/system.sources</code>.</p>
<p>That&rsquo;s It! You should now have Hyprland installed and when you reboot, you should be able to select Hyprland in the
GDM login screen, after selecting the user, click the settings and then select Hyprland. An example below showing different
versions of Gnome.</p>
<p> <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
<details
  class="notice caution"
  open="true"
>
    <summary class="notice-title">dpkg install</summary>
  
  I originally used <code>dpkg</code> to install the deb it will overwrite
your sources.list file. So make sure you revert back to the original version if you do that!
</details>

<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://gist.github.com/Vertecedoc4545/3b077301299c20c5b9b4db00f4ca6000?permalink_comment_id=4595603#gistcomment-4595603">https://gist.github.com/Vertecedoc4545/3b077301299c20c5b9b4db00f4ca6000?permalink_comment_id=4595603#gistcomment-4595603</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Image Source: <a href="https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/">https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Title Your Terminals When Running Tmux</title>
      <link>https://haseebmajid.dev/posts/2023-11-22-til-how-to-title-your-terminals-when-running-tmux/</link>
      <pubDate>Wed, 22 Nov 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-11-22-til-how-to-title-your-terminals-when-running-tmux/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Title Your Terminals When Running Tmux&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I have been tmux with the foot terminal and when trying to share my screen I couldn&amp;rsquo;t work out which terminal to share
based on the name. Since they are running tmux, all the terminals were titled &lt;code&gt;terminal - t&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To fix this and name it after what is running (and where) in tmux, i.e. &lt;code&gt;foot blog/nvim&lt;/code&gt;. We can do this by adding the
following to our tmux config:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Title Your Terminals When Running Tmux</strong></p>
<p>I have been tmux with the foot terminal and when trying to share my screen I couldn&rsquo;t work out which terminal to share
based on the name. Since they are running tmux, all the terminals were titled <code>terminal - t</code>.</p>
<p>To fix this and name it after what is running (and where) in tmux, i.e. <code>foot blog/nvim</code>. We can do this by adding the
following to our tmux config:</p>
<pre tabindex="0"><code class="language-tmux" data-lang="tmux">set-option -g set-titles on
set-option -g set-titles-string &#34;#S / #W&#34;
</code></pre><p>Here, the <code>#S</code> is the session name and <code>#W</code> the window name; hence, we get something like the above. Though you could use
lots of other different formats <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</p>
<p>This is enough for me to figure out which window to share.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://github.com/tmux/tmux/wiki/Formats">https://github.com/tmux/tmux/wiki/Formats</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Start Gnome Wayland Session From TTY</title>
      <link>https://haseebmajid.dev/posts/2023-11-21-til-how-to-start-gnome-wayland-session-from-tty/</link>
      <pubDate>Tue, 21 Nov 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-11-21-til-how-to-start-gnome-wayland-session-from-tty/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Start Gnome Wayland Session From TTY&lt;/strong&gt;&lt;/p&gt;
&lt;details
  class=&#34;notice info&#34;
  open=&#34;true&#34;
&gt;
    &lt;summary class=&#34;notice-title&#34;&gt;Better Version&lt;/summary&gt;
  
  &lt;p&gt;veganomy posted a better version: &lt;a href=&#34;https://github.com/hmajid2301/blog/issues/2&#34;&gt;https://github.com/hmajid2301/blog/issues/2&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Copy &lt;code&gt;org.gnome.Shell.desktop&lt;/code&gt; file locally:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ cp /usr/share/applications/org.gnome.Shell.desktop ~/.local/share/applications/org.gnome.Shell-Wayland.desktop
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Edit it so that it so that it runs &lt;code&gt;gnome-shell&lt;/code&gt; as a wayland compositor:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ less ~/.local/share/applications/org.gnome.Shell-Wayland.desktop
...
Exec=/usr/bin/gnome-shell --wayland
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Copy &lt;code&gt;gnome.session&lt;/code&gt; file locally:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ cp /usr/share/gnome-session/sessions/gnome.session ~/.config/gnome-session/sessions/gnome-wayland.session
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Edit it to use &lt;code&gt;org.gnome.Shell-Wayland&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ less ~/.config/gnome-session/sessions/gnome-wayland.session
...
RequiredComponents=org.gnome.Shell-Wayland;gnome-settings-daemon;
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now create an executable &lt;code&gt;~/.local/bin/gnome&lt;/code&gt; :&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;
#!/bin/sh

[ -r /bin/gnome-session ] &amp;amp;&amp;amp; {
	export \
		EGL_PLATFORM=wayland \
		GDK_BACKEND=wayland \
		XDG_SESSION_DESKTOP=gnome
	/bin/gnome-session --session=gnome-wayland
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Make sure this directory is in your &lt;code&gt;~/.bash_profile&lt;/code&gt; $PATH : &lt;code&gt;export PATH=~/.local/bin:$PATH&lt;/code&gt;&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Start Gnome Wayland Session From TTY</strong></p>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">Better Version</summary>
  
  <p>veganomy posted a better version: <a href="https://github.com/hmajid2301/blog/issues/2">https://github.com/hmajid2301/blog/issues/2</a></p>
<p>Copy <code>org.gnome.Shell.desktop</code> file locally:</p>
<pre tabindex="0"><code>$ cp /usr/share/applications/org.gnome.Shell.desktop ~/.local/share/applications/org.gnome.Shell-Wayland.desktop
</code></pre><p>Edit it so that it so that it runs <code>gnome-shell</code> as a wayland compositor:</p>
<pre tabindex="0"><code>$ less ~/.local/share/applications/org.gnome.Shell-Wayland.desktop
...
Exec=/usr/bin/gnome-shell --wayland
...
</code></pre><p>Copy <code>gnome.session</code> file locally:</p>
<pre tabindex="0"><code>$ cp /usr/share/gnome-session/sessions/gnome.session ~/.config/gnome-session/sessions/gnome-wayland.session
</code></pre><p>Edit it to use <code>org.gnome.Shell-Wayland</code>:</p>
<pre tabindex="0"><code>$ less ~/.config/gnome-session/sessions/gnome-wayland.session
...
RequiredComponents=org.gnome.Shell-Wayland;gnome-settings-daemon;
...
</code></pre><p>Now create an executable <code>~/.local/bin/gnome</code> :</p>
<pre tabindex="0"><code>
#!/bin/sh

[ -r /bin/gnome-session ] &amp;&amp; {
	export \
		EGL_PLATFORM=wayland \
		GDK_BACKEND=wayland \
		XDG_SESSION_DESKTOP=gnome
	/bin/gnome-session --session=gnome-wayland
}
</code></pre><p>Make sure this directory is in your <code>~/.bash_profile</code> $PATH : <code>export PATH=~/.local/bin:$PATH</code></p>
<p>Now you can run a wayland session through TTY :</p>
<pre tabindex="0"><code>$ gnome
</code></pre>
</details>

<p>Recently, I moved to Hyprland on Ubuntu. I wanted to start gnome in another TTY (teletype). It was more effort to find than I expected:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Go to teletype</span>
</span></span><span class="line"><span class="cl">CTRL+ALT+1
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">dbus-run-session -- gnome-shell --display-server --wayland
</span></span></code></pre></div><p>That&rsquo;s it, short and sweet, this post! You now started gnome in a Wayland session.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>How I Setup My Raspberry Pi Cluster With Nixos</title>
      <link>https://haseebmajid.dev/posts/2023-11-18-how-i-setup-my-raspberry-pi-cluster-with-nixos/</link>
      <pubDate>Sat, 18 Nov 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-11-18-how-i-setup-my-raspberry-pi-cluster-with-nixos/</guid>
      <description>&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;p&gt;Recently, I proceeded to experiment with some Raspberry PIs (RPI) that I had lying around. I wanted to do something
with them, so I decided I would turn them into a k8s cluster and put various random tools that might be nice to have
on it. Such as a GitLab runner, Jellyfin media server &amp;amp; pi hole for ad blocking.&lt;/p&gt;
&lt;h2 id=&#34;hardware&#34;&gt;Hardware&lt;/h2&gt;
&lt;p&gt;The list below shows the things I used to set up my rpi cluster. None of this is sponsored! I paid for everything
listed below:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="background">Background</h2>
<p>Recently, I proceeded to experiment with some Raspberry PIs (RPI) that I had lying around. I wanted to do something
with them, so I decided I would turn them into a k8s cluster and put various random tools that might be nice to have
on it. Such as a GitLab runner, Jellyfin media server &amp; pi hole for ad blocking.</p>
<h2 id="hardware">Hardware</h2>
<p>The list below shows the things I used to set up my rpi cluster. None of this is sponsored! I paid for everything
listed below:</p>
<p><img
        loading="lazy"
        src="/posts/2023-11-18-how-i-setup-my-raspberry-pi-cluster-with-nixos/images/pi-cluster-front.jpeg"
        type=""
        alt="Pi Cluster Front"
        
      />
<img
        loading="lazy"
        src="/posts/2023-11-18-how-i-setup-my-raspberry-pi-cluster-with-nixos/images/pi-cluster-side.jpeg"
        type=""
        alt="Pi Cluster Side"
        
      /></p>
<ul>
<li>Utronics SSD Cluster Case for Raspberry Pi
<ul>
<li>SKU: U6244</li>
</ul>
</li>
<li>2x Raspberry Pi 4 - 4GB RAM</li>
<li>2x Raspberry Pi 4 - 8GB RAM</li>
<li>2x 2TB SSD</li>
<li>2x 2TB Hard Disks 2.5&quot;</li>
<li>4x SSD to USB 3.0 Cable for Raspberry Pi</li>
<li>4x Raspberry Pi PoE+ HAT</li>
<li>4x MicroSD Extender Set for Uctronics Cluster Cases
<ul>
<li>Easier to access SD cards in the case</li>
</ul>
</li>
<li>4x 16 GB Micro SD Cards</li>
<li>4x CAT7 Ethernet Cables 0.5M
<ul>
<li>Could&rsquo;ve gone a bit smaller (you can see in the photos)</li>
</ul>
</li>
<li>Powerline Adapter
<ul>
<li>P-Link TL-WPA4220</li>
</ul>
</li>
<li>TP-Link 5-Port Gigabit Desktop PoE Switch
<ul>
<li>TL-SG1005P</li>
</ul>
</li>
<li>Double sided Velcro
<ul>
<li>Attaches switch to Utronics case</li>
</ul>
</li>
</ul>
<p>Extra:</p>
<ul>
<li>Micro SD card to USB Adapter</li>
<li>For flashing the image with NixOS</li>
</ul>
<h2 id="setup">Setup</h2>
<h3 id="power-over-ethernet">Power Over Ethernet</h3>
<p>So I learnt I could power my rpis using Ethernet, power over Ethernet (PoE). This saves us needing an extra cable on each
pi, I had a USB hub on top of my pi cluster (on top of the switch).</p>
<p>To use POE, we need to have a switch which can be used for POE, we also need a PoE hat on the Raspberry Pi. Then finally
an Ethernet cable from the switch to the Raspberry Pi. We still need to power the switch from our mains. Since my rpis
will be away from my router, I also use a powerline adapter for a more stable internet connection. So 4 of the ports
in the switch connects to the rpis and the 5th connects to a powerline adapter.</p>
<p><img
        loading="lazy"
        src="/posts/2023-11-18-how-i-setup-my-raspberry-pi-cluster-with-nixos/images/ethernet-hat.jpeg"
        type=""
        alt="Ethernet Hat"
        
      /></p>
<h3 id="ssd-to-usb">SSD to USB</h3>
<p>Since the SD cards I got only are 16GB for more persistent storage, I used some of my spares SSDs and hard disks.
Once nice thing was the case has a nice slot for the SSDs to be placed (under the pis).</p>
<p><img
        loading="lazy"
        src="/posts/2023-11-18-how-i-setup-my-raspberry-pi-cluster-with-nixos/images/side-view.jpeg"
        type=""
        alt="Side View"
        
      /></p>
<p>Then we use an adapter to convert the SSDs to USB 3.0 on the pi. So we can still access the SSD, with the PI. For
more persistent data storage.</p>
<p><img
        loading="lazy"
        src="/posts/2023-11-18-how-i-setup-my-raspberry-pi-cluster-with-nixos/images/ssd-to-usb.jpeg"
        type=""
        alt="SSD to USB"
        
      /></p>
<h3 id="install-nixos">Install NixOS</h3>
<p>I followed this <a href="https://nix.dev/tutorials/nixos/installing-nixos-on-a-raspberry-pi">tutorial</a>, to setup NixOS on
my pi cluster. We will need to repeat this process for each of pis (4x).</p>
<p>Assuming our host is running nix we can do:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="n">nix-shell</span> <span class="err">-</span><span class="n">p</span> <span class="n">wget</span> <span class="n">zstd</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">wget</span> <span class="sd">https://hydra.nixos.org/build/226381178/download/1/nixos-sd-image-23.11pre541036.63678e9f3d3a-aarch64-linux.img.zst</span>
</span></span><span class="line"><span class="cl"><span class="n">unzstd</span> <span class="err">-</span><span class="n">d</span> <span class="n">nixos-sd-image-23</span><span class="o">.</span><span class="err">11</span><span class="n">pre500597</span><span class="o">.</span><span class="err">0</span><span class="n">fbe93c5a7c-aarch64-linux</span><span class="o">.</span><span class="n">img</span><span class="o">.</span><span class="n">zst</span>
</span></span><span class="line"><span class="cl"><span class="n">dmesg</span> <span class="err">--</span><span class="n">follow</span>
</span></span></code></pre></div><p>Plug in your SD card and your terminal should print what device it got assigned, for example /dev/sdX.
Press Ctrl+C to stop dmesg &ndash;follow.</p>
<p>Then finally we can do:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="n">sudo</span> <span class="n">dd</span> <span class="k">if</span><span class="err">=</span><span class="n">nixos-sd-image-23</span><span class="o">.</span><span class="err">11</span><span class="n">pre541036</span><span class="o">.</span><span class="err">63678</span><span class="n">e9f3d3a-aarch64-linux</span><span class="o">.</span><span class="n">img</span> <span class="n">of</span><span class="err">=</span><span class="sr">/dev/sda</span> <span class="n">bs</span><span class="err">=</span><span class="mi">4096</span> <span class="n">conv</span><span class="err">=</span><span class="n">fsync</span> <span class="n">status</span><span class="err">=</span><span class="n">progres</span>
</span></span></code></pre></div><p>Then put the SD card in your pi. Before we can start deploying to them
we need to get some bare-bones setup we can SSH to the machine.</p>
<p>Then I connect the PI to a keyboard and monitor, so I can see the IP address and set a default password on the <code>nixos</code>
user.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="n">passwd</span> <span class="n">nixos</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">#123456</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ifconfig</span>
</span></span></code></pre></div><p>Then we can ssh to the pi from the same network, i.e. my main NixOS machine. By doing something like:</p>
<p><code>ssh nixos@192.168.0.75</code>, replace 192.168.0.75 with the IP address of your pi, returned from the ifconfig command.
Then create a new config file:</p>
<h3 id="ssh">ssh</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">nix-shell -p git vim
</span></span><span class="line"><span class="cl">sudo vim /etc/nixos/configuration.nix
</span></span></code></pre></div><p>With the following contents:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"> <span class="p">{</span><span class="n">config</span><span class="o">,</span> <span class="n">pkgs</span><span class="o">,</span> <span class="n">lib</span><span class="o">,</span> <span class="o">...</span> <span class="p">}:</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">let</span>
</span></span><span class="line"><span class="cl">  <span class="n">hostname</span> <span class="o">=</span> <span class="s2">&#34;strawberry&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">in</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">imports</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;</span><span class="si">${</span><span class="nb">builtins</span><span class="o">.</span><span class="n">fetchGit</span> <span class="p">{</span> <span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;https://github.com/NixOS/nixos-hardware.git&#34;</span><span class="p">;</span> <span class="p">}</span><span class="si">}</span><span class="s2">/raspberry-pi/4&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">boot</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">kernelPackages</span> <span class="o">=</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">linuxKernel</span><span class="o">.</span><span class="n">packages</span><span class="o">.</span><span class="n">linux_rpi4</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">initrd</span><span class="o">.</span><span class="n">availableKernelModules</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;xhci_pci&#34;</span> <span class="s2">&#34;usbhid&#34;</span> <span class="s2">&#34;usb_storage&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="n">loader</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">grub</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">generic-extlinux-compatible</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">fileSystems</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;/&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">device</span> <span class="o">=</span> <span class="s2">&#34;/dev/disk/by-label/NIXOS_SD&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">fsType</span> <span class="o">=</span> <span class="s2">&#34;ext4&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">options</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;noatime&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">networking</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">hostName</span> <span class="o">=</span> <span class="n">hostname</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">environment</span><span class="o">.</span><span class="n">systemPackages</span> <span class="o">=</span> <span class="k">with</span> <span class="n">pkgs</span><span class="p">;</span> <span class="p">[</span> <span class="n">git</span> <span class="n">vim</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="n">nix</span><span class="o">.</span><span class="n">settings</span><span class="o">.</span><span class="n">trusted-users</span> <span class="o">=</span> <span class="p">[</span> <span class="n">hostname</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">services</span><span class="o">.</span><span class="n">openssh</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">settings</span><span class="o">.</span><span class="n">PasswordAuthentication</span> <span class="o">=</span> <span class="no">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">settings</span><span class="o">.</span><span class="n">KbdInteractiveAuthentication</span> <span class="o">=</span> <span class="no">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">users</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">users</span><span class="o">.</span><span class="s2">&#34;</span><span class="si">${</span><span class="n">hostname</span><span class="si">}</span><span class="s2">&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">isNormalUser</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">extraGroups</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;wheel&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">      <span class="c1"># Note: This is not very secure, I only use this password for a little while whilst the PI </span>
</span></span><span class="line"><span class="cl">      <span class="c1"># can only be accessible from my local network</span>
</span></span><span class="line"><span class="cl">      <span class="n">password</span> <span class="o">=</span> <span class="n">hostname</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">openssh</span><span class="o">.</span><span class="n">authorizedKeys</span><span class="o">.</span><span class="n">keys</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;ssh-ed25519 &lt;YOUR_PUB_KEY&gt;&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="n">security</span><span class="o">.</span><span class="n">sudo</span><span class="o">.</span><span class="n">wheelNeedsPassword</span> <span class="o">=</span> <span class="no">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">services</span><span class="o">.</span><span class="n">avahi</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">nssmdns</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">publish</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">addresses</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">domain</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">hinfo</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">userServices</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">workstation</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">hardware</span><span class="o">.</span><span class="n">enableRedistributableFirmware</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">system</span><span class="o">.</span><span class="n">stateVersion</span> <span class="o">=</span> <span class="s2">&#34;23.11&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Finally, <code>sudo nixos-rebuild switch</code> to build this new configuration.</p>
<p>This setups a few things on my machine, with the <code>avahi</code> service, we can connect to the pi using the host name instead of
the IP address. This saves needing to create a static IP for each pi. So I can now connect to this pi like:</p>
<p><code>ssh strawberry@strawberry.local</code></p>
<p>We also don&rsquo;t need a password as I can use my ssh key to log in</p>
<h4 id="firmware-update">Firmware Update</h4>
<p>If we want to update our pi firmware, we can do something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">nix-shell -p raspberrypi-eeprom
</span></span><span class="line"><span class="cl">mount /dev/disk/by-label/FIRMWARE /mnt
</span></span><span class="line"><span class="cl"><span class="nv">BOOTFS</span><span class="o">=</span>/mnt <span class="nv">FIRMWARE_RELEASE_STATUS</span><span class="o">=</span>stable rpi-eeprom-update -d -a
</span></span></code></pre></div><p>That&rsquo;s It! We&rsquo;ve set up our rpi cluster with NixOS, and we can connect to it using the hostname. In the next couple of
tutorials we will look at how we can use Nix tools to manage the deployment and deploy k3s to the cluster.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Debug a Test in Golang With Build Tags in Neovim</title>
      <link>https://haseebmajid.dev/posts/2023-11-16-til-how-to-debug-a-test-in-golang-with-build-tags-in-neovim/</link>
      <pubDate>Thu, 16 Nov 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-11-16-til-how-to-debug-a-test-in-golang-with-build-tags-in-neovim/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Debug a Test in Golang With Build Tags in Neovim&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I was having issues with my debugger today (well technically yesterday because I am publishing this a day later to spread
out my blog posts but same difference) and it took me a few hours to realise what was going on. In my case, I was
trying to debug a test written in Golang using &lt;code&gt;nvim-dap-go&lt;/code&gt; on Neovim.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Debug a Test in Golang With Build Tags in Neovim</strong></p>
<p>I was having issues with my debugger today (well technically yesterday because I am publishing this a day later to spread
out my blog posts but same difference) and it took me a few hours to realise what was going on. In my case, I was
trying to debug a test written in Golang using <code>nvim-dap-go</code> on Neovim.</p>
<p>The reason the test was failing  because the test file had build tags so delve (the debugger) couldn&rsquo;t compile
the file, i.e. using the unit build tag in the example below.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">//go:build unit
</span></span></span><span class="line"><span class="cl"><span class="c1">// +build unit
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="kn">package</span> <span class="nx">options_test</span>
</span></span></code></pre></div><p>When we set up the <code>dag-go</code> plugin, we need to pass an extra field called <code>build_flags</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="n">lua</span> <span class="n">require</span><span class="p">(</span><span class="s1">&#39;dap-go&#39;</span><span class="p">).</span><span class="n">setup</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">delve</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line hl"><span class="cl">    <span class="n">build_flags</span> <span class="o">=</span> <span class="s2">&#34;-tags=unit,integration,e2e&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Replace the above with the tags you use! If you are using NixVim you should be able to do something like this <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">programs</span><span class="o">.</span><span class="n">nixvim</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">plugins</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">dap</span><span class="o">.</span><span class="n">extensions</span><span class="o">.</span><span class="n">dap-go</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">delve</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">path</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">delve</span><span class="si">}</span><span class="s2">/bin/dlv&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">          <span class="n">buildFlags</span> <span class="o">=</span> <span class="s2">&#34;-tags=unit,integration,e2e&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Then I have a keybinding to debug the nearest test like so, again using NixVim.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">action</span> <span class="o">=</span> <span class="s2">&#34;&lt;cmd&gt; lua require(&#39;dap-go&#39;).debug_test()&lt;CR&gt;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">key</span> <span class="o">=</span> <span class="s2">&#34;&lt;leader&gt;td&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">options</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Debug Nearest (Go)&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="n">mode</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;n&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span></code></pre></div><p>in lua:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;n&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;leader&gt;td&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;cmd&gt;lua require(&#39;dap-go&#39;).debug_test()&lt;CR&gt;&#34;</span><span class="p">)</span>
</span></span></code></pre></div><div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>After this PR is merged, <a href="https://github.com/nix-community/nixvim/pull/703">https://github.com/nix-community/nixvim/pull/703</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>Part 3: Hyprland as Part of Your Development Workflow</title>
      <link>https://haseebmajid.dev/posts/2023-11-15-part-3-hyprland-as-part-of-your-development-workflow/</link>
      <pubDate>Wed, 15 Nov 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-11-15-part-3-hyprland-as-part-of-your-development-workflow/</guid>
      <description>&lt;h2 id=&#34;preamble&#34;&gt;Preamble&lt;/h2&gt;
&lt;p&gt;Now we have looked at our choice of hardware, which OS to use and specifically, how to configure NixOS (at a high-level),
using a git repository.&lt;/p&gt;
&lt;p&gt;In this part, we will go over which window manager to use. There are two main types we could use here. Either a desktop
environment like Gnome or KDE. Which comes with batteries included, it provides us with everything we need and we
don&amp;rsquo;t need to configure much to get stuff working. Things like a notification daemon, polkit (for auth).&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="preamble">Preamble</h2>
<p>Now we have looked at our choice of hardware, which OS to use and specifically, how to configure NixOS (at a high-level),
using a git repository.</p>
<p>In this part, we will go over which window manager to use. There are two main types we could use here. Either a desktop
environment like Gnome or KDE. Which comes with batteries included, it provides us with everything we need and we
don&rsquo;t need to configure much to get stuff working. Things like a notification daemon, polkit (for auth).</p>
<p>Whereas in a more bare-bones window manager such as Hyprland, i3, Sway they are far more bare bones and we need to
setup a lot more things ourselves. They are more customisable but more effort to set up, they also tend to be more light
weight on resources.</p>
<p>In this section, we will look at using a tiling window manager <a href="https://wiki.hyprland.org/">Hyprland</a>,
though, you could also look at using <a href="https://swaywm.org/">Sway</a>.</p>
<h2 id="why-a-tiling-manager">Why a tiling manager?</h2>
<p>A tiling window manager is great because it handles placing our window automatically for us. This allows for more
efficient usage of our space. Rather than multiple overlapping windows, each window has its own space on the display.</p>
<p></p>
<p>It also makes it much easier to be keyboard-driven, so we need to rely on our mouse a lot less. We can use the keyboard
to swap between open windows. We can also open apps using a tool like <code>rofi</code> again from the keyboard. This just helps
us be quicker, not wasting time needing to reach for the mouse.</p>
<h2 id="why-hyprland">Why Hyprland?</h2>
<p>I mean, between Sway and Hyprland I don&rsquo;t think there is much difference, the main reason I chose to use Hyprland was because
I saw plenty of people ricing it on unixporn, especially with NixOS. So I used some of their setups as inspiration.</p>
<p>Hyprland also has a few nice compositor tweaks like transparency and animations. Which I think makes things more interesting.
But any other Wayland tiling manager will do, such as Sway or River. I don&rsquo;t have a super strong reason to use Hyprland.</p>
<p>Saying that, since plenty of people seem to be using it, it&rsquo;s easy to find example setups and copy them or nick bits I like.</p>
<h2 id="how-do-i-use-hyprland">How do I use Hyprland?</h2>
<p>So you may be wondering how does a window manager fits in to our development workflow. Specifically, a tiling window manager.
Typically, I will have 5–6 workspaces in use:</p>
<ul>
<li>1: Coding (main monitor)
<ul>
<li>Entirely dedicated to my code editor (Neovim in my terminal)
<ul>
<li>Usually has a few splits for different buffers</li>
</ul>
</li>
</ul>
</li>
<li>2: Firefox (secondary monitor)
<ul>
<li>Two Firefox windows
<ul>
<li>One playing random YouTube videos</li>
<li>The other with documentation or related to the work I am currently doing</li>
</ul>
</li>
</ul>
</li>
<li>3: Secondary project
<ul>
<li>Contains terminal with Neovim</li>
<li>Firefox window related to project</li>
</ul>
</li>
<li>4: Monkeytype
<ul>
<li>Firefox for me to practice my typing when I get bored</li>
</ul>
</li>
<li>5: Social Apps
<ul>
<li>Ferdi</li>
<li>Discord</li>
</ul>
</li>
<li>6: Gaming
<ul>
<li>Steam</li>
<li>Lutris</li>
</ul>
</li>
</ul>
<p>I mainly use workspace 1 &amp; 2, I also use <a href="https://github.com/ofirgall/tmux-browser">tmux-browser</a> which means
I can manage my browser tabs per tmux session (more on this in a future post). Where the open Firefox window will often
be one managed by tmux-browser.</p>
<h2 id="wayland">Wayland</h2>
<p>I have personally decided to try to use Wayland compilable window managers. I know people have issues with Wayland.
But for the most part, Wayland just seems to work fine for me. I originally swapped to Wayland because I had two monitors
and I wanted to set the refresh rate of each separately. Whereas XServer treats them as one display, so I had to set it
60Hz, but now I can have one monitor on 144Hz and another on 60Hz.</p>
<p>Note your mileage may vary, and you may well have issues with Wayland, especially if you are using an Nvidia GPU.</p>
<h1 id="setup">Setup</h1>
<p>One of the things I like about NixOS is it makes it straightforward to add new packages and config.</p>
<h2 id="flake-input">flake input</h2>
<p>First, update our flake inputs to add Hyprland.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="n">hyprland</span><span class="o">.</span><span class="n">url</span> <span class="err">=</span> <span class="s2">&#34;github:hyprwm/Hyprland&#34;</span><span class="p">;</span>
</span></span></code></pre></div><h2 id="home-manager">home-manager</h2>
<p>So, having a look at my <code>hyprland.nix</code> file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span> <span class="n">inputs</span>
</span></span><span class="line"><span class="cl"><span class="o">,</span> <span class="n">config</span>
</span></span><span class="line"><span class="cl"><span class="o">,</span> <span class="n">pkgs</span>
</span></span><span class="line"><span class="cl"><span class="o">,</span> <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}:</span>
</span></span><span class="line"><span class="cl"><span class="k">let</span>
</span></span><span class="line"><span class="cl">  <span class="n">laptop_lid_switch</span> <span class="o">=</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">writeShellScriptBin</span> <span class="s2">&#34;laptop_lid_switch&#34;</span> <span class="s1">&#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">    #!/usr/bin/env bash
</span></span></span><span class="line"><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="cl"><span class="s1">    if grep open /proc/acpi/button/lid/LID0/state; then
</span></span></span><span class="line"><span class="cl"><span class="s1">        hyprctl keyword monitor &#34;eDP-1, 2256x1504@60, 0x0, 1&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">    else
</span></span></span><span class="line"><span class="cl"><span class="s1">        if [[ `hyprctl monitors | grep &#34;Monitor&#34; | wc -l` != 1 ]]; then
</span></span></span><span class="line"><span class="cl"><span class="s1">            hyprctl keyword monitor &#34;eDP-1, disable&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">        fi
</span></span></span><span class="line"><span class="cl"><span class="s1">    fi
</span></span></span><span class="line"><span class="cl"><span class="s1">  &#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">in</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">imports</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="n">inputs</span><span class="o">.</span><span class="n">hyprland</span><span class="o">.</span><span class="n">homeManagerModules</span><span class="o">.</span><span class="n">default</span>
</span></span><span class="line"><span class="cl">    <span class="sr">./common</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">home</span><span class="o">.</span><span class="n">packages</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="n">inputs</span><span class="o">.</span><span class="n">hypr-contrib</span><span class="o">.</span><span class="n">packages</span><span class="o">.</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">system</span><span class="si">}</span><span class="o">.</span><span class="n">grimblast</span>
</span></span><span class="line"><span class="cl">    <span class="n">inputs</span><span class="o">.</span><span class="n">hyprland</span><span class="o">.</span><span class="n">packages</span><span class="o">.</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">system</span><span class="si">}</span><span class="o">.</span><span class="n">xdg-desktop-portal-hyprland</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">nix</span><span class="o">.</span><span class="n">settings</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">substituters</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;https://hyprland.cachix.org&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="n">trusted-public-keys</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;hyprland.cachix.org-1:a7pgxzMz7+chwVL3/pzj6jIBMioiJM7ypFP8PwtkuGc=&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">wayland</span><span class="o">.</span><span class="n">windowManager</span><span class="o">.</span><span class="n">hyprland</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># TODO: move to https://github.com/spikespaz/hyprland-nix</span>
</span></span><span class="line"><span class="cl">    <span class="n">extraConfig</span> <span class="o">=</span> <span class="s1">&#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">      # ASCII Art from https://fsymbols.com/generators/carty/
</span></span></span><span class="line"><span class="cl"><span class="s1">      input {
</span></span></span><span class="line"><span class="cl"><span class="s1">      	kb_layout = gb
</span></span></span><span class="line"><span class="cl"><span class="s1">      	touchpad {
</span></span></span><span class="line"><span class="cl"><span class="s1">      		disable_while_typing=false
</span></span></span><span class="line"><span class="cl"><span class="s1">      	}
</span></span></span><span class="line"><span class="cl"><span class="s1">      }
</span></span></span><span class="line"><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="cl"><span class="s1">      general {
</span></span></span><span class="line"><span class="cl"><span class="s1">      	gaps_in = 3
</span></span></span><span class="line"><span class="cl"><span class="s1">      	gaps_out = 5
</span></span></span><span class="line"><span class="cl"><span class="s1">      	border_size = 3
</span></span></span><span class="line"><span class="cl"><span class="s1">      	col.active_border=0xff</span><span class="si">${</span><span class="n">config</span><span class="o">.</span><span class="n">colorscheme</span><span class="o">.</span><span class="n">colors</span><span class="o">.</span><span class="n">base07</span><span class="si">}</span><span class="s1">
</span></span></span><span class="line"><span class="cl"><span class="s1">      	col.inactive_border=0xff</span><span class="si">${</span><span class="n">config</span><span class="o">.</span><span class="n">colorscheme</span><span class="o">.</span><span class="n">colors</span><span class="o">.</span><span class="n">base02</span><span class="si">}</span><span class="s1">
</span></span></span><span class="line"><span class="cl"><span class="s1">      }
</span></span></span><span class="line"><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="cl"><span class="s1">      decoration {
</span></span></span><span class="line"><span class="cl"><span class="s1">      	rounding=5
</span></span></span><span class="line"><span class="cl"><span class="s1">      }
</span></span></span><span class="line"><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="cl"><span class="s1">      misc {
</span></span></span><span class="line"><span class="cl"><span class="s1">       vrr = 2
</span></span></span><span class="line"><span class="cl"><span class="s1">       disable_hyprland_logo = 1;
</span></span></span><span class="line"><span class="cl"><span class="s1">      }
</span></span></span><span class="line"><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="cl"><span class="s1">      $notifycmd = notify-send -h string:x-canonical-private-synchronous:hypr-cfg -u low
</span></span></span><span class="line"><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="cl"><span class="s1">      # █▀ █░█ █▀█ █▀█ ▀█▀ █▀▀ █░█ ▀█▀ █▀
</span></span></span><span class="line"><span class="cl"><span class="s1">      # ▄█ █▀█ █▄█ █▀▄ ░█░ █▄▄ █▄█ ░█░ ▄█
</span></span></span><span class="line"><span class="cl"><span class="s1">      bind = SUPER, Return, exec, </span><span class="si">${</span><span class="n">config</span><span class="o">.</span><span class="n">my</span><span class="o">.</span><span class="n">settings</span><span class="o">.</span><span class="n">default</span><span class="o">.</span><span class="n">terminal</span><span class="si">}</span><span class="s1">
</span></span></span><span class="line"><span class="cl"><span class="s1">      bind = SUPER, b, exec, </span><span class="si">${</span><span class="n">config</span><span class="o">.</span><span class="n">my</span><span class="o">.</span><span class="n">settings</span><span class="o">.</span><span class="n">default</span><span class="o">.</span><span class="n">browser</span><span class="si">}</span><span class="s1">
</span></span></span><span class="line"><span class="cl"><span class="s1">      bind = SUPER, a, exec, rofi -show drun -modi drun
</span></span></span><span class="line"><span class="cl"><span class="s1">      bind = ALT, Tab, exec, rofi -show window
</span></span></span><span class="line"><span class="cl"><span class="s1">      bind = SUPER, w, exec, makoctl dismiss
</span></span></span><span class="line"><span class="cl"><span class="s1">      # ...
</span></span></span><span class="line"><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="cl"><span class="s1">    &#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>I cut out some config so it&rsquo;s easier to follow the file above. By going over it section by section:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl">  <span class="n">laptop_lid_switch</span> <span class="err">=</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">writeShellScriptBin</span> <span class="s2">&#34;laptop_lid_switch&#34;</span> <span class="s1">&#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">    #!/usr/bin/env bash
</span></span></span><span class="line"><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="cl"><span class="s1">    if grep open /proc/acpi/button/lid/LID0/state; then
</span></span></span><span class="line"><span class="cl"><span class="s1">        hyprctl keyword monitor &#34;eDP-1, 2256x1504@60, 0x0, 1&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">    else
</span></span></span><span class="line"><span class="cl"><span class="s1">        if [[ `hyprctl monitors | grep &#34;Monitor&#34; | wc -l` != 1 ]]; then
</span></span></span><span class="line"><span class="cl"><span class="s1">            hyprctl keyword monitor &#34;eDP-1, disable&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">        fi
</span></span></span><span class="line"><span class="cl"><span class="s1">    fi
</span></span></span><span class="line"><span class="cl"><span class="s1">  &#39;&#39;</span><span class="p">;</span>
</span></span></code></pre></div><p>This first bit, a simple bash script which is used to switch off the display on a laptop when the lid is closed. We will
see how it is used later, we give it the name <code>laptop_lid_switch</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl">  <span class="n">imports</span> <span class="err">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="n">inputs</span><span class="o">.</span><span class="n">hyprland</span><span class="o">.</span><span class="n">homeManagerModules</span><span class="o">.</span><span class="n">default</span>
</span></span><span class="line"><span class="cl">    <span class="sr">./common</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">home</span><span class="o">.</span><span class="n">packages</span> <span class="err">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="n">inputs</span><span class="o">.</span><span class="n">hyprland</span><span class="o">.</span><span class="n">packages</span><span class="o">.</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">system</span><span class="si">}</span><span class="o">.</span><span class="n">xdg-desktop-portal-hyprland</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">nix</span><span class="o">.</span><span class="n">settings</span> <span class="err">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">substituters</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;https://hyprland.cachix.org&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="n">trusted-public-keys</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;hyprland.cachix.org-1:a7pgxzMz7+chwVL3/pzj6jIBMioiJM7ypFP8PwtkuGc=&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span></code></pre></div><p>The next bit, we import our packages <code>inputs.hyprland.homeManagerModules.default</code>, using the input we defined before.
Which handles things like creating a config file for us. I also have some common settings I like to share between
my window managers, like GTK settings.</p>
<p>Next, we install <code>xdg-desktop-portal-hyprland</code>, which lets other apps communicate with the compositor through d-bus.
Think for use cases like screen sharing.</p>
<blockquote>
<p>An XDG Desktop Portal (later called XDP) is a program that lets other applications communicate swiftly with the compositor through D-Bus.</p>
</blockquote>
<p>Then finally we set some nix settings, to point to a new binary cache to pull Hyprland dependencies from as they aren&rsquo;t
built on the normal nix cache. This saves us time building the Hyprland deps locally.</p>
<p>The next bit, we specify our Hyprland options in Nix:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">wayland</span><span class="o">.</span><span class="n">windowManager</span><span class="o">.</span><span class="n">hyprland</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">extraConfig</span> <span class="o">=</span> <span class="s1">&#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">      # ASCII Art from https://fsymbols.com/generators/carty/
</span></span></span><span class="line"><span class="cl"><span class="s1">      input {
</span></span></span><span class="line"><span class="cl"><span class="s1">      	kb_layout = gb
</span></span></span><span class="line"><span class="cl"><span class="s1">      	touchpad {
</span></span></span><span class="line"><span class="cl"><span class="s1">      		disable_while_typing=false
</span></span></span><span class="line"><span class="cl"><span class="s1">      	}
</span></span></span><span class="line"><span class="cl"><span class="s1">      }
</span></span></span><span class="line"><span class="cl"><span class="s1">    &#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>For now, using the options available in home-manager, I enable Hyprland. Then basically the rest of the config is in
<code>extraConfig</code>, we just pass it a string, and it writes it to the <code>~/.config/hypr/hyprland.conf</code>.
More options here: <a href="https://mipmip.github.io/home-manager-option-search/?query=hyprland">https://mipmip.github.io/home-manager-option-search/?query=hyprland</a></p>
<blockquote>
<p>At some point Hyprland may be configured differently using, <a href="https://github.com/spikespaz/hyprland-nix">https://github.com/spikespaz/hyprland-nix</a></p>
</blockquote>
<h4 id="config">config</h4>
<p>Going over the most interesting bits of my Hyprland config:</p>
<pre tabindex="0"><code>bind = SUPER, Return, exec, ${config.my.settings.default.terminal}
bind = SUPER, b, exec, ${config.my.settings.default.browser}
bind = SUPER, a, exec, rofi -show drun -modi drun
bind = ALT, Tab, exec, rofi -show window
</code></pre><p>Here we set some key bindings such as how to open my terminal and Rofi my app launcher. Probably my two most key bindings,
especially Rofi, how I open new apps in Hyprland.</p>
<pre tabindex="0"><code>windowrulev2 = fullscreen, title:^(Guild Wars 2)$
windowrulev2 = idleinhibit focus, class:^(mpv)$
windowrulev2 = idleinhibit fullscreen, class:^(firefox)$
</code></pre><p>Next, we define some exceptions to our rules, such as Guild Wars 2 (video game) starting in full-screen mode by default.
Then, if Firefox is in full screen, i.e. when watching a video, stop idling from occurring.</p>
<pre tabindex="0"><code>exec-once = swaync &amp;
exec-once = kanshi &amp;
exec-once = sway-audio-idle-inhibit -w &amp;
exec-once = waybar &amp;
exec-once = gammastep-indicator &amp;
exec-once = swaybg -i ${config.my.settings.wallpaper} --mode fill &amp;
</code></pre><p>Next we define a bunch of apps we want to be run on startup, such as swaync for notifications, swaybg for wallpaper
and gammastep-indicator to tell us when our blue light filter is on.</p>
<pre tabindex="0"><code>bindl=,switch:Lid Switch, exec, ${laptop_lid_switch}
</code></pre><p>Here, we use it to run our laptop lid switch script every time our laptop lid status changes.</p>
<pre tabindex="0"><code>bind=,XF86MonBrightnessUp,exec, brightness --inc
bind=,XF86MonBrightnessDown,exec, brightness --dec
bind=,XF86AudioRaiseVolume,exec, volume --inc
bind=,XF86AudioLowerVolume,exec, volume --dec
bind=,XF86AudioMute,exec, volume --toggle
bind=,XF86AudioMicMute,exec, volume --toggle-mic
bind=,XF86AudioNext,exec,playerctl next
bind=,XF86AudioPrev,exec,playerctl previous
bind=,XF86AudioPlay,exec,playerctl play-pause
bind=,XF86AudioStop,exec,playerctl stop
bind=ALT,XF86AudioNext,exec,playerctld shift
bind=ALT,XF86AudioPrev,exec,playerctld unshift
bind=ALT,XF86AudioPlay,exec,systemctl --user restart playerctld
</code></pre><p>The rest of the config is just key bindings, this bit is around &ldquo;special&rdquo; keys on my keyboard either the volume control
knob or laptop function keys to increase volume and brightness. Where <code>brightness</code> and <code>volume</code> are custom scripts
I have defined myself. We will see them a bit later.</p>
<pre tabindex="0"><code>bind=SUPER,h,movefocus,l
bind=SUPER,l,movefocus,r
bind=SUPER,k,movefocus,u
bind=SUPER,j,movefocus,d
</code></pre><p>The next bit of my config is all around window management. Note all of these bits will use a combination of the vim
keys to do various things, i.e. here pressing super+l changes the focus to the window to the right. To simplify I will
just show the first key binding, but there will be 4 key bindings for each of the following.</p>
<ul>
<li><code>bind=SUPER,h,movefocus,l</code>: Pressing <code>SUPER+h</code> will change focus to a window on the left of the current focused one.</li>
<li><code>bind=SUPERSHIFT,h,swapwindow,l</code>: Pressing <code>SHIFT+SUPER+h</code> will swap the current focused window with the one to the left of it</li>
<li><code>bind=SUPERCONTROL,h,focusmonitor,l</code>: Pressing <code>CTRL+SUPER+h</code> will swap the focus to the monitor on the left</li>
<li><code>bind=SUPERALT,h,movecurrentworkspacetomonitor,l</code>: Pressing <code>ALT+SUPER+h</code> will move the current workspace (all the windows) to the monitor on the left</li>
<li><code>bind=SUPERALT,h,movecurrentworkspacetomonitor,l</code>: Pressing <code>ALT+SUPER+h</code> will move the current workspace (all the windows) to the monitor on the left</li>
</ul>
<pre tabindex="0"><code> bind=SUPER,1,workspace,01
</code></pre><p>One of our most important commands, changes to that workspace when pressing <code>SUPER+1</code>. I have workspaces from 0 to 9.
Again, assume, for the following commands, it goes from workspace 1 - 10.</p>
<pre tabindex="0"><code>bind=SUPERSHIFT,1,movetoworkspacesilent,01
</code></pre><p>This command moves the current focused window to a specific workspace, i.e. <code>SUPER+SHIFT+1</code> moves the window to workspace 1.</p>
<pre tabindex="0"><code>bind = SUPER, Q, killactive,
bind = SUPER, F, fullscreen, 0
bind = SUPER, F, exec, $notifycmd &#39;Fullscreen Mode&#39;
bind = SUPER, S, pseudo,
bind = SUPER, S, exec, $notifycmd &#39;Pseudo Mode&#39;
bind = SUPER, Space, togglefloating,
bind = SUPER, Space, centerwindow,
</code></pre><p>These commands can be used to change our windows, I use <code>SUPER + Q</code> a lot to kill the focused window.
The other can be used to turn an app full screen, or let it float or even move it centre. Which can be useful in
specific circumstances.</p>
<pre tabindex="0"><code>bindm=SUPER, mouse:272, movewindow
bindm=SUPER, mouse:273, resizewindow

binde = SUPERALT, h, resizeactive, -20 0
binde = SUPERALT, l, resizeactive, 20 0
binde = SUPERALT, k, resizeactive, 0 -20
binde = SUPERALT, j, resizeactive, 0 20
</code></pre><p>Finally, we have a bunch of commands for resizing windows, either using the keyboard or our mouse (sometimes we all feel lazy).</p>
<h2 id="nixos">NixOS</h2>
<p>Setting up Hyprland using home-manager is not enough, we also need to set it in NixOS. The main thing being is we need
to import <code>inputs.hyprland.nixosModules.default</code>. This sets up a bunch of stuff we will need, such as polkit etc.</p>
<h3 id="greetd">greetd</h3>
<p>Then finally, we need a way to launch Hyprland when we log in, I decided to use <code>greetd</code> to do that for us.
Here, we tell it to run the <code>Hyprland</code> command for the user <code>haseeb</code> (I have set up auto-login).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">services</span><span class="o">.</span><span class="n">greetd</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">settings</span> <span class="o">=</span> <span class="k">rec</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">initial_session</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">command</span> <span class="o">=</span> <span class="s2">&#34;Hyprland&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">user</span> <span class="o">=</span> <span class="s2">&#34;haseeb&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">      <span class="n">default_session</span> <span class="o">=</span> <span class="n">initial_session</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="n">environment</span><span class="o">.</span><span class="n">etc</span><span class="o">.</span><span class="s2">&#34;greetd/environments&#34;</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="s1">&#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">    Hyprland
</span></span></span><span class="line"><span class="cl"><span class="s1">  &#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h2 id="other-apps">Other Apps</h2>
<p>That&rsquo;s it in terms of setting up Hyprland; however, there are some tools that we should look at setting up with Hyprland
to make it more usable and closer to a desktop environment such as Gnome.</p>
<ul>
<li>rofi: Mainly used to launch apps</li>
<li>grimblast: Used to take screenshots</li>
<li>gammastep-indicator: Blue light filter</li>
<li>waybar: The top bar, which shows useful information like internet connection, volume and memory usage</li>
<li>kanshi: Used to set up display layouts</li>
<li>swaync: Used to receive notifications</li>
<li>swaybg: Used to set the wallpaper</li>
</ul>
<p>In a future article, I will do a deeper dive into these apps and how I use them in my workflow. But this post has
gone on long enough for now! In the next main part, we will look at which terminal emulator I use.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/dotfiles/-/blob/59bf75086353721f1c6902e5811ac30b6148faff/home-manager/desktops/wms/hyprland.nix">My Hyprland Nix Config, at the time of writing</a></li>
<li><a href="https://wiki.hyprland.org/0.19.1beta">Hyprland Wiki</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Install Neovim Nightly Using Nix Home Manager (and NixVim)</title>
      <link>https://haseebmajid.dev/posts/2023-11-15-til-how-to-install-neovim-nightly-using-nix-home-manager/</link>
      <pubDate>Wed, 15 Nov 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-11-15-til-how-to-install-neovim-nightly-using-nix-home-manager/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Install Neovim Nightly Using Nix Home Manager (and NixVim)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Recently, I wanted to install the nightly version of Neovim (version 0.10) because it supports inlay hints.
However, on nixpkgs the latest version of Neovim as of writing is 0.9.4. So how can we get the nightly release?
Using nix/home-manager.&lt;/p&gt;
&lt;p&gt;Simple, we can use an overlay that will add the Neovim nightly package with the nightly. Assuming we are using nix flakes.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Install Neovim Nightly Using Nix Home Manager (and NixVim)</strong></p>
<p>Recently, I wanted to install the nightly version of Neovim (version 0.10) because it supports inlay hints.
However, on nixpkgs the latest version of Neovim as of writing is 0.9.4. So how can we get the nightly release?
Using nix/home-manager.</p>
<p>Simple, we can use an overlay that will add the Neovim nightly package with the nightly. Assuming we are using nix flakes.</p>
<p>Add the nightly Neovim as an input:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"> <span class="n">inputs</span><span class="o">.</span><span class="n">neovim-nightly-overlay</span><span class="o">.</span><span class="n">url</span> <span class="err">=</span> <span class="s2">&#34;github:nix-community/neovim-nightly-overlay&#34;</span><span class="p">;</span>
</span></span></code></pre></div><p>Then add the actual overlay, I do this in my home manager config <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl">  <span class="n">nixpkgs</span> <span class="err">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">overlays</span> <span class="o">=</span>  <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="n">inputs</span><span class="o">.</span><span class="n">neovim-nightly-overlay</span><span class="o">.</span><span class="n">overlay</span>
</span></span><span class="line"><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span></code></pre></div><p>Then in another module we can do something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="n">home</span><span class="o">.</span><span class="n">packages</span> <span class="err">=</span> <span class="k">with</span> <span class="n">pkgs</span> <span class="p">[</span><span class="n">neovim-nightly</span><span class="p">]</span>
</span></span></code></pre></div><p>or if you are using NixVim <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> like me:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">programs</span><span class="o">.</span><span class="n">nixvim</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">extraPlugins</span> <span class="o">=</span> <span class="k">with</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">vimPlugins</span><span class="p">;</span> <span class="p">[</span> <span class="n">plenary-nvim</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="n">package</span> <span class="o">=</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">neovim-nightly</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://gitlab.com/hmajid2301/dotfiles/-/blob/ed8f60b6f27980508161d337e0a75ebb655cb19b/home-manager/default.nix#L52">https://gitlab.com/hmajid2301/dotfiles/-/blob/ed8f60b6f27980508161d337e0a75ebb655cb19b/home-manager/default.nix#L52</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://github.com/nix-community/nixvim">https://github.com/nix-community/nixvim</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Set Network Manager Priority to Use Wired Connection Over WIFI</title>
      <link>https://haseebmajid.dev/posts/2023-11-10-how-to-set-network-manager-priority-to-use-wired-connection-over-wifi/</link>
      <pubDate>Fri, 10 Nov 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-11-10-how-to-set-network-manager-priority-to-use-wired-connection-over-wifi/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Set Network Manager Priority to Use Wired Connection Over Wi-Fi&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you use network manager on Linux and have both Wi-Fi and wired connection. You may want to prefer using
a wired connection over Wi-Fi, due to stability. To do open the &lt;code&gt;nm-connection-editor&lt;/code&gt;, if you are using Nix, you can
download it from nixpkgs like usual.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Higher number means higher priority.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So for our wired connection I set the priority to &lt;code&gt;100&lt;/code&gt; (it was previously -1) and then for our Wi-Fi connection
I set the priority to &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Set Network Manager Priority to Use Wired Connection Over Wi-Fi</strong></p>
<p>If you use network manager on Linux and have both Wi-Fi and wired connection. You may want to prefer using
a wired connection over Wi-Fi, due to stability. To do open the <code>nm-connection-editor</code>, if you are using Nix, you can
download it from nixpkgs like usual.</p>
<p></p>
<blockquote>
<p>Higher number means higher priority.</p>
</blockquote>
<p>So for our wired connection I set the priority to <code>100</code> (it was previously -1) and then for our Wi-Fi connection
I set the priority to <code>1</code>.</p>
<p>That&rsquo;s it! We should now see network manager prefer to use Ethernet over Wi-Fi.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>A Simple Way to Convert JSON to Nix Attribute Sets</title>
      <link>https://haseebmajid.dev/posts/2023-11-06-a-simple-way-to-convert-json-to-nix-attribute-sets/</link>
      <pubDate>Mon, 06 Nov 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-11-06-a-simple-way-to-convert-json-to-nix-attribute-sets/</guid>
      <description>&lt;p&gt;In this post, I will show you how you can take some JSON and convert it into a Nix attribute set.
This was particularly useful when I was creating my waybar configuration. Which is usually in JSON, but defined in my
home-manager Nix config it has to be in nixlang.&lt;/p&gt;
&lt;p&gt;So given this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;s2&#34;&gt;&amp;#34;custom/notification&amp;#34;&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;tooltip&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;format&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;{icon}&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;format-icons&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nt&#34;&gt;&amp;#34;notification&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;lt;span foreground=&amp;#39;red&amp;#39;&amp;gt;&amp;lt;sup&amp;gt;&amp;lt;/sup&amp;gt;&amp;lt;/span&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nt&#34;&gt;&amp;#34;none&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nt&#34;&gt;&amp;#34;dnd-notification&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;lt;span foreground=&amp;#39;red&amp;#39;&amp;gt;&amp;lt;sup&amp;gt;&amp;lt;/sup&amp;gt;&amp;lt;/span&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nt&#34;&gt;&amp;#34;dnd-none&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nt&#34;&gt;&amp;#34;inhibited-notification&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;lt;span foreground=&amp;#39;red&amp;#39;&amp;gt;&amp;lt;sup&amp;gt;&amp;lt;/sup&amp;gt;&amp;lt;/span&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nt&#34;&gt;&amp;#34;inhibited-none&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nt&#34;&gt;&amp;#34;dnd-inhibited-notification&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;lt;span foreground=&amp;#39;red&amp;#39;&amp;gt;&amp;lt;sup&amp;gt;&amp;lt;/sup&amp;gt;&amp;lt;/span&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nt&#34;&gt;&amp;#34;dnd-inhibited-none&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;return-type&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;json&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;exec-if&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;which swaync-client&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;exec&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;swaync-client -swb&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;on-click&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;swaync-client -t -sw&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;on-click-right&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;swaync-client -d -sw&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;escape&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We want it to look something like:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this post, I will show you how you can take some JSON and convert it into a Nix attribute set.
This was particularly useful when I was creating my waybar configuration. Which is usually in JSON, but defined in my
home-manager Nix config it has to be in nixlang.</p>
<p>So given this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl">  <span class="s2">&#34;custom/notification&#34;</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;tooltip&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;format&#34;</span><span class="p">:</span> <span class="s2">&#34;{icon}&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;format-icons&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;notification&#34;</span><span class="p">:</span> <span class="s2">&#34;&lt;span foreground=&#39;red&#39;&gt;&lt;sup&gt;&lt;/sup&gt;&lt;/span&gt;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;none&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;dnd-notification&#34;</span><span class="p">:</span> <span class="s2">&#34;&lt;span foreground=&#39;red&#39;&gt;&lt;sup&gt;&lt;/sup&gt;&lt;/span&gt;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;dnd-none&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;inhibited-notification&#34;</span><span class="p">:</span> <span class="s2">&#34;&lt;span foreground=&#39;red&#39;&gt;&lt;sup&gt;&lt;/sup&gt;&lt;/span&gt;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;inhibited-none&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;dnd-inhibited-notification&#34;</span><span class="p">:</span> <span class="s2">&#34;&lt;span foreground=&#39;red&#39;&gt;&lt;sup&gt;&lt;/sup&gt;&lt;/span&gt;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;dnd-inhibited-none&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;return-type&#34;</span><span class="p">:</span> <span class="s2">&#34;json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;exec-if&#34;</span><span class="p">:</span> <span class="s2">&#34;which swaync-client&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;exec&#34;</span><span class="p">:</span> <span class="s2">&#34;swaync-client -swb&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;on-click&#34;</span><span class="p">:</span> <span class="s2">&#34;swaync-client -t -sw&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;on-click-right&#34;</span><span class="p">:</span> <span class="s2">&#34;swaync-client -d -sw&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;escape&#34;</span><span class="p">:</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span><span class="err">,</span>
</span></span></code></pre></div><p>We want it to look something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="s2">&#34;custom/notification&#34;</span> <span class="err">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">tooltip</span> <span class="o">=</span> <span class="no">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">format</span> <span class="o">=</span> <span class="s2">&#34;{} {icon} &#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;format-icons&#34;</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">notification</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="se">\u</span><span class="s2">f0a2&lt;span foreground=&#39;red&#39;&gt;&lt;sup&gt;</span><span class="se">\u</span><span class="s2">f444&lt;/sup&gt;&lt;/span&gt;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">none</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="se">\u</span><span class="s2">f0a2&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;dnd-notification&#34;</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="se">\u</span><span class="s2">f1f7&lt;span foreground=&#39;red&#39;&gt;&lt;sup&gt;</span><span class="se">\u</span><span class="s2">f444&lt;/sup&gt;&lt;/span&gt;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;dnd-none&#34;</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="se">\u</span><span class="s2">f1f7&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;inhibited-notification&#34;</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="se">\u</span><span class="s2">f0a2&lt;span foreground=&#39;red&#39;&gt;&lt;sup&gt;</span><span class="se">\u</span><span class="s2">f444&lt;/sup&gt;&lt;/span&gt;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;inhibited-none&#34;</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="se">\u</span><span class="s2">f0a2&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;dnd-inhibited-notification&#34;</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="se">\u</span><span class="s2">f1f7&lt;span foreground=&#39;red&#39;&gt;&lt;sup&gt;</span><span class="se">\u</span><span class="s2">f444&lt;/sup&gt;&lt;/span&gt;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;dnd-inhibited-none&#34;</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="se">\u</span><span class="s2">f1f7&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;return-type&#34;</span> <span class="o">=</span> <span class="s2">&#34;json&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;exec-if&#34;</span> <span class="o">=</span> <span class="s2">&#34;which swaync-client&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">exec</span> <span class="o">=</span> <span class="s2">&#34;swaync-client -swb&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;on-click&#34;</span> <span class="o">=</span> <span class="s2">&#34;swaync-client -t -sw&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;on-click-right&#34;</span> <span class="o">=</span> <span class="s2">&#34;swaync-client -d -sw&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">escape</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>As you can see, it looks very similar, but there are enough differences to make it annoying by hand.
I originally came across
<a href="https://gist.githubusercontent.com/Scoder12/0538252ed4b82d65e59115075369d34d/raw/e86d1d64d1373a497118beb1259dab149cea951d/json2nix.py">this script</a>.</p>
<p>Which basically did everything I needed. I ended up improving a few things like needing to surround the block in <code>{}</code>
for it to be valid JSON. I also removed a comma if it existed, because JSON will complain about that as well.</p>
<p>My version of the <a href="https://gitlab.com/-/snippets/3613708">script can be found here</a></p>
]]></content:encoded>
    </item>
    
    <item>
      <title>How Can You Export Your Atuin History to Fish History?</title>
      <link>https://haseebmajid.dev/posts/2023-11-05-how-can-you-export-your-atuin-history-to-fish-history/</link>
      <pubDate>Sun, 05 Nov 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-11-05-how-can-you-export-your-atuin-history-to-fish-history/</guid>
      <description>&lt;p&gt;I have made an &lt;a href=&#34;https://haseebmajid.dev/2023-08-12-how-sync-your-shell-history-with-atuin-in-nix/&#34;&gt;post in the past&lt;/a&gt; about how you can set up
&lt;a href=&#34;https://atuin.sh/&#34;&gt;Atuin&lt;/a&gt; to sync share history across multiple devices.&lt;/p&gt;
&lt;p&gt;Whilst this works great and does the job, fish shell doesn&amp;rsquo;t have the same history that Atuin does. Sometimes
we want to have better suggestions in Fish. For example, when you start to type fish shell will suggest the last command
in your history that best matches what you are typing (see example below).&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I have made an <a href="/2023-08-12-how-sync-your-shell-history-with-atuin-in-nix/">post in the past</a> about how you can set up
<a href="https://atuin.sh/">Atuin</a> to sync share history across multiple devices.</p>
<p>Whilst this works great and does the job, fish shell doesn&rsquo;t have the same history that Atuin does. Sometimes
we want to have better suggestions in Fish. For example, when you start to type fish shell will suggest the last command
in your history that best matches what you are typing (see example below).</p>
<p></p>
<p>To resolve this issue, we can use a go script that I
<a href="https://github.com/atuinsh/atuin/issues/1073#issuecomment-1610861147">came across here</a> that can be used to export
the Atuin data (in a sqlite db), to our fish history file. Which is typically found in <code>~/.local/state/fish/fish_history</code>.</p>
<p>I then put this on my <a href="https://gitlab.com/hmajid2301/atuin-export-fish-history">GitLab here</a>.
To run the script, we could do something like <code>go install gitlab.com/hmajid2301/atuin-export-fish-history</code>. Then run
<code>atuin-export-fish-history</code>.</p>
<p>However, if you&rsquo;re running NixOS or Nix package manager, you might want to install it using Nix like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span> <span class="n">lib</span>
</span></span><span class="line"><span class="cl"><span class="o">,</span> <span class="n">buildGoModule</span>
</span></span><span class="line"><span class="cl"><span class="o">,</span> <span class="n">fetchFromGitLab</span>
</span></span><span class="line"><span class="cl"><span class="p">}:</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">buildGoModule</span> <span class="k">rec</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">pname</span> <span class="o">=</span> <span class="s2">&#34;atuin-export-fish-history&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">version</span> <span class="o">=</span> <span class="s2">&#34;0.1.0&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">src</span> <span class="o">=</span> <span class="n">fetchFromGitLab</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">owner</span> <span class="o">=</span> <span class="s2">&#34;hmajid2301&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">repo</span> <span class="o">=</span> <span class="n">pname</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">rev</span> <span class="o">=</span> <span class="s2">&#34;v</span><span class="si">${</span><span class="n">version</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">sha256</span> <span class="o">=</span> <span class="s2">&#34;sha256-2egZYLnaekcYm2IzPdWAluAZogdi4Nf/oXWLw8+AnMk=&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">vendorHash</span> <span class="o">=</span> <span class="s2">&#34;sha256-hLEmRq7Iw0hHEAla0Ehwk1EfmpBv6ddBuYtq12XdhVc=&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">ldflags</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;-s&#34;</span> <span class="s2">&#34;-w&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Then we can use it like <code>atuin-export-fish-history</code> like normal.</p>
<p>That&rsquo;s it! We could probably do to automate this a bit more to run it on a schedule as cronjob or run it on system startup.
However, that&rsquo;s for another blog post!</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Use Clipboard Data as File Input in Bash</title>
      <link>https://haseebmajid.dev/posts/2023-11-01-til-how-to-use-clipboard-data-as-file-input-in-bash/</link>
      <pubDate>Wed, 01 Nov 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-11-01-til-how-to-use-clipboard-data-as-file-input-in-bash/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Use Clipboard Data as File Input in Bash&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Recently, I wanted to run a bash script where it needed to receive a JSON file as input. However, the JSON I had
was taken from somewhere on the internet. In this case, it was taking a JSON blob and converting it to a nix attribute
set. However, I didn&amp;rsquo;t want to save the contents to a file beforehand.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Use Clipboard Data as File Input in Bash</strong></p>
<p>Recently, I wanted to run a bash script where it needed to receive a JSON file as input. However, the JSON I had
was taken from somewhere on the internet. In this case, it was taking a JSON blob and converting it to a nix attribute
set. However, I didn&rsquo;t want to save the contents to a file beforehand.</p>
<p>This is the command here as an example, note the <code>/dev/stdin</code> which acts as file like object for us.</p>
<pre tabindex="0"><code>wl-paste | json2nix /dev/stdin
</code></pre><p>So <code>wl-paste</code> returns the contents of our clipboard (our JSON). Then we pipe that over to our <code>json2nix</code> script which
expects a file. We can pretend to have a file by using the <code>/dev/stdin</code> <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://jameshfisher.com/2018/03/31/dev-stdout-stdin/">https://jameshfisher.com/2018/03/31/dev-stdout-stdin/</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Setup a Go Development Shell With Nix Flakes</title>
      <link>https://haseebmajid.dev/posts/2023-10-26-how-to-setup-a-go-development-shell-with-nix-flakes/</link>
      <pubDate>Thu, 26 Oct 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-10-26-how-to-setup-a-go-development-shell-with-nix-flakes/</guid>
      <description>&lt;p&gt;As you may know, I have been using Nix/NixOS for the last few months. I finally started doing some development, after
spending lots and lots and lots of time tweaking my setup (and neovim).&lt;/p&gt;
&lt;p&gt;As part of starting to do some real development work, I am now trying to leverage devshells with Nix flakes.
I like the concept of Nix devshells, I have tried using Docker dev containers in the past, but the issue I had
with those was adding my tools such as shell (fish) or cli tools was not easy. Whereas Nix shells just add
tools and scripts to our existing shell.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>As you may know, I have been using Nix/NixOS for the last few months. I finally started doing some development, after
spending lots and lots and lots of time tweaking my setup (and neovim).</p>
<p>As part of starting to do some real development work, I am now trying to leverage devshells with Nix flakes.
I like the concept of Nix devshells, I have tried using Docker dev containers in the past, but the issue I had
with those was adding my tools such as shell (fish) or cli tools was not easy. Whereas Nix shells just add
tools and scripts to our existing shell.</p>
<p>By using Nix flakes we can guarantee (or close to) that developers will be using the same versions of all the tools,
provided in the devshell.</p>
<h2 id="flake-template">Flake Template</h2>
<p>First, we make sure you have support for Nix flakes <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>. To get started let&rsquo;s use a flake template to create a new flake
in our go project. First, make sure you are in the root of your project i.e. where <code>go.mod</code> is and then run
<code>nix flake init -t github:nix-community/gomod2nix#app</code> <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>.</p>
<details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">Fix</summary>
  
  <p>At the moment the created flake is broken, on line 25 we have to fix this.
Remove <code>buildGoApplication</code> so the line looks like <code>inherit (gomod2nix.legacyPackages.${system}) mkGoEnv gomod2nix;</code>.</p>
<p>See this <a href="https://github.com/nix-community/gomod2nix/pull/133/files">PR</a> for more information.</p>

</details>

<h3 id="direnv">direnv</h3>
<p>I would recommend enabling <code>direnv</code> which is a tool that allows us to run commands automatically when entering a
directory there is a version available for <a href="https://github.com/nix-community/nix-direnv">nix</a>. This will cache our Nix
development shell and make it much faster to run after the first run. Also prevents the garbage collector from removing
build dependencies we need for our nix shells.</p>
<p>We can enable nix-direnv in home-manager like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">programs</span><span class="o">.</span><span class="n">direnv</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">nix-direnv</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="gomod2nix">gomod2nix</h3>
<p>Then after entering the development shell either via <code>direnv</code> or running <code>nix develop</code>, run the following command:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">gomod2nix generate
</span></span></code></pre></div><p>This will populate the <code>gomod2nix.toml</code> file with information about our dependencies:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="nx">schema</span> <span class="p">=</span> <span class="mi">3</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">mod</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">[</span><span class="nx">mod</span><span class="p">.</span><span class="s2">&#34;github.com/PuerkitoBio/goquery&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="nx">version</span> <span class="p">=</span> <span class="s2">&#34;v1.8.1&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">hash</span> <span class="p">=</span> <span class="s2">&#34;sha256-z2RaB8PVPEzSJdMUfkfNjT616yXWTjW2gkhNOh989ZU=&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">[</span><span class="nx">mod</span><span class="p">.</span><span class="s2">&#34;github.com/andybalholm/cascadia&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="nx">version</span> <span class="p">=</span> <span class="s2">&#34;v1.3.1&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">hash</span> <span class="p">=</span> <span class="s2">&#34;sha256-M0u22DXSeXUaYtl1KoW1qWL46niFpycFkraCEQ/luYA=&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">[</span><span class="nx">mod</span><span class="p">.</span><span class="s2">&#34;github.com/davecgh/go-spew&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="nx">version</span> <span class="p">=</span> <span class="s2">&#34;v1.1.1&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">hash</span> <span class="p">=</span> <span class="s2">&#34;sha256-nhzSUrE1fCkN0+RL04N4h8jWmRFPPPWbCuDc7Ss0akI=&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">[</span><span class="nx">mod</span><span class="p">.</span><span class="s2">&#34;github.com/pmezard/go-difflib&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="nx">version</span> <span class="p">=</span> <span class="s2">&#34;v1.0.0&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">hash</span> <span class="p">=</span> <span class="s2">&#34;sha256-/FtmHnaGjdvEIKAJtrUfEhV7EVo5A/eYrtdnUkuxLDA=&#34;</span>
</span></span></code></pre></div><h2 id="adding-extra-packages">Adding extra packages</h2>
<p>Now how we can add extra packages to our Nix shell? Simply go to our <code>shell.nix</code> file and find the bit where
we specify the <code>pkgs.mkShell</code>. Then here we can add the packages we want available, such as say <code>golangci-lint</code> or
<code>gotools</code> to have goimports tool available.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="n">pkgs</span><span class="o">.</span><span class="n">mkShell</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">hardeningDisable</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;all&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="n">packages</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="n">goEnv</span>
</span></span><span class="line"><span class="cl">    <span class="n">gomod2nix</span>
</span></span><span class="line"><span class="cl">    <span class="n">pkgs</span><span class="o">.</span><span class="n">golangci-lint</span>
</span></span><span class="line"><span class="cl">    <span class="n">pkgs</span><span class="o">.</span><span class="n">go_1_21</span>
</span></span><span class="line"><span class="cl">    <span class="n">pkgs</span><span class="o">.</span><span class="n">gotools</span>
</span></span><span class="line"><span class="cl">    <span class="n">pkgs</span><span class="o">.</span><span class="n">go-junit-report</span>
</span></span><span class="line"><span class="cl">    <span class="n">pkgs</span><span class="o">.</span><span class="n">go-task</span>
</span></span><span class="line"><span class="cl">    <span class="n">pkgs</span><span class="o">.</span><span class="n">delve</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Now our nix shell will have these tools available including go version 1.21. It&rsquo;d be nice to find a way to specify
the go version in the go.mod file and just use that version.</p>
<h3 id="flakelock">flake.lock</h3>
<p>We can check the flake.lock which makes sure that
when we share this repository other developers will get the same version of the tools we did. As the flake.lock
specifies a specific git revision until we do a flake update, this will include nixpkgs which are a set of nix
expressions git repo.</p>
<p>So updating the flake will update the revision of nixpkgs, which may then include the expression
to build a newer version of say <code>golangci-lint</code>. However again this will be the same for all developers once they have
pulled in our changes and rebuilt their dev shell. Which makes our development environment far more reproducible.</p>
<h2 id="pre-commit">pre-commit</h2>
<p>Now that we have packages available, we can also add pre-commit hooks to our development shell. Using the popular
pre-commit tool. First, we need to add a new input to our flake.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="n">inputs</span><span class="o">.</span><span class="n">nixpkgs</span><span class="o">.</span><span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;github:NixOS/nixpkgs/nixos-unstable&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">inputs</span><span class="o">.</span><span class="n">flake-utils</span><span class="o">.</span><span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;github:numtide/flake-utils&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">inputs</span><span class="o">.</span><span class="n">gomod2nix</span><span class="o">.</span><span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;github:nix-community/gomod2nix&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">inputs</span><span class="o">.</span><span class="n">gomod2nix</span><span class="o">.</span><span class="n">inputs</span><span class="o">.</span><span class="n">nixpkgs</span><span class="o">.</span><span class="n">follows</span> <span class="o">=</span> <span class="s2">&#34;nixpkgs&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">inputs</span><span class="o">.</span><span class="n">gomod2nix</span><span class="o">.</span><span class="n">inputs</span><span class="o">.</span><span class="n">flake-utils</span><span class="o">.</span><span class="n">follows</span> <span class="o">=</span> <span class="s2">&#34;flake-utils&#34;</span><span class="p">;</span>
</span></span><span class="line hl"><span class="cl"><span class="n">inputs</span><span class="o">.</span><span class="n">pre-commit-hooks</span><span class="o">.</span><span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;github:cachix/pre-commit-hooks.nix&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Then add the pre-commits as an argument to our outputs and make sure its accessible to our <code>devShell</code>, this is where
we will set up our pre-commit hooks.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="n">outputs</span> <span class="err">=</span> <span class="p">{</span> <span class="n">self</span><span class="o">,</span> <span class="n">nixpkgs</span><span class="o">,</span> <span class="n">flake-utils</span><span class="o">,</span> <span class="n">gomod2nix</span><span class="o">,</span> <span class="n">pre-commit-hooks</span><span class="o">,</span> <span class="o">...</span> <span class="p">}:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">devShells</span><span class="o">.</span><span class="n">default</span> <span class="o">=</span> <span class="n">callPackage</span> <span class="sr">./shell.nix</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">inherit</span> <span class="p">(</span><span class="n">gomod2nix</span><span class="o">.</span><span class="n">legacyPackages</span><span class="o">.</span><span class="si">${</span><span class="n">system</span><span class="si">}</span><span class="p">)</span> <span class="n">mkGoEnv</span> <span class="n">gomod2nix</span><span class="p">;</span>
</span></span><span class="line hl"><span class="cl">    <span class="k">inherit</span> <span class="n">pre-commit-hooks</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Then in our <code>shell.nix</code> file we want it to look something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span> <span class="n">pkgs</span> 
</span></span><span class="line"><span class="cl"><span class="o">,</span> <span class="n">mkGoEnv</span> <span class="o">?</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">mkGoEnv</span>
</span></span><span class="line"><span class="cl"><span class="o">,</span> <span class="n">gomod2nix</span> <span class="o">?</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">gomod2nix</span>
</span></span><span class="line"><span class="cl"><span class="o">,</span> <span class="n">pre-commit-hooks</span>
</span></span><span class="line"><span class="cl"><span class="o">,</span> <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}:</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">let</span>
</span></span><span class="line"><span class="cl">  <span class="n">pre-commit-check</span> <span class="o">=</span> <span class="n">pre-commit-hooks</span><span class="o">.</span><span class="n">lib</span><span class="o">.</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">system</span><span class="si">}</span><span class="o">.</span><span class="n">run</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">src</span> <span class="o">=</span> <span class="sr">./.</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">hooks</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">gofmt</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">golangci-lint</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;golangci-lint&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span> <span class="o">=</span> <span class="s2">&#34;Lint my golang code&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">files</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="se">\.</span><span class="s2">go$&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">entry</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">golangci-lint</span><span class="si">}</span><span class="s2">/bin/golangci-lint run --new-from-rev HEAD --fix&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">require_serial</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">pass_filenames</span> <span class="o">=</span> <span class="no">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">      <span class="n">goimports</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;goimports&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span> <span class="o">=</span> <span class="s2">&#34;Format my golang code&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">files</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="se">\.</span><span class="s2">go$&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">entry</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl">          <span class="k">let</span>
</span></span><span class="line"><span class="cl">            <span class="n">script</span> <span class="o">=</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">writeShellScript</span> <span class="s2">&#34;precommit-goimports&#34;</span> <span class="s1">&#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">              set -e
</span></span></span><span class="line"><span class="cl"><span class="s1">              failed=false
</span></span></span><span class="line"><span class="cl"><span class="s1">              for file in &#34;$@&#34;; do
</span></span></span><span class="line"><span class="cl"><span class="s1">                  # redirect stderr so that violations and summaries are properly interleaved.
</span></span></span><span class="line"><span class="cl"><span class="s1">                  if ! </span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">gotools</span><span class="si">}</span><span class="s1">/bin/goimports -l -d &#34;$file&#34; 2&gt;&amp;1
</span></span></span><span class="line"><span class="cl"><span class="s1">                  then
</span></span></span><span class="line"><span class="cl"><span class="s1">                      failed=true
</span></span></span><span class="line"><span class="cl"><span class="s1">                  fi
</span></span></span><span class="line"><span class="cl"><span class="s1">              done
</span></span></span><span class="line"><span class="cl"><span class="s1">              if [[ $failed == &#34;true&#34; ]]; then
</span></span></span><span class="line"><span class="cl"><span class="s1">                  exit 1
</span></span></span><span class="line"><span class="cl"><span class="s1">              fi
</span></span></span><span class="line"><span class="cl"><span class="s1">            &#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">          <span class="k">in</span>
</span></span><span class="line"><span class="cl">          <span class="nb">builtins</span><span class="o">.</span><span class="nb">toString</span> <span class="n">script</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="k">in</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">pkgs</span><span class="o">.</span><span class="n">mkShell</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">inherit</span> <span class="p">(</span><span class="n">pre-commit-check</span><span class="p">)</span> <span class="n">shellHook</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>When we enter our nix shell it will automatically install pre-commit hooks and the yaml file <code>.pre-commit-config.yaml</code>
(We should add this file to a gitignore). That&rsquo;s all we need to get our pre-commit.</p>
<h2 id="build-go-binary">Build go binary</h2>
<p>To build our binary using Nix we can simply run <code>nix run</code>, where we can see how this works in our <code>default.nix</code>
file. Particularly the part with <code>buildGoApplication</code> <sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="n">buildGoApplication</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">pname</span> <span class="o">=</span> <span class="s2">&#34;myapp&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">version</span> <span class="o">=</span> <span class="s2">&#34;0.1&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">pwd</span> <span class="o">=</span> <span class="sr">./.</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">src</span> <span class="o">=</span> <span class="sr">./.</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">modules</span> <span class="o">=</span> <span class="sr">./gomod2nix.toml</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>That&rsquo;s it! We set up a development shell using Nix flakes for our go project. Including adding pre-commits and how
we can build our Go binary using nix. Leveraging the <code>gomod2nix</code> tool.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://nixos.wiki/wiki/Flakes">https://nixos.wiki/wiki/Flakes</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://www.tweag.io/blog/2021-03-04-gomod2nix/">https://www.tweag.io/blog/2021-03-04-gomod2nix/</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p><a href="https://github.com/nix-community/gomod2nix/blob/master/docs/nix-reference.md">https://github.com/nix-community/gomod2nix/blob/master/docs/nix-reference.md</a>&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>Part 2: How to Setup Nixos as Part of Your Development Workflow</title>
      <link>https://haseebmajid.dev/posts/2023-10-24-part-2-how-to-setup-nixos-as-part-of-your-development-workflow/</link>
      <pubDate>Tue, 24 Oct 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-10-24-part-2-how-to-setup-nixos-as-part-of-your-development-workflow/</guid>
      <description>&lt;h2 id=&#34;premable&#34;&gt;Premable&lt;/h2&gt;
&lt;p&gt;In this second part of the series, we will look at how we can not set up NixOS past installation. How we can install
software and various other tools. After part 1 we should have NixOS installed, mind you since I&amp;rsquo;ve written that blog
post I found a way to create a custom ISO image from my Nix config. This ISO contains a custom install script,
the main advantage being able to use a tool called &lt;a href=&#34;https://github.com/diskonauts/disko&#34;&gt;disko&lt;/a&gt; to partition our disks.
Anyway, I will probably write another post in the future going over how you can do this in another blog post.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="premable">Premable</h2>
<p>In this second part of the series, we will look at how we can not set up NixOS past installation. How we can install
software and various other tools. After part 1 we should have NixOS installed, mind you since I&rsquo;ve written that blog
post I found a way to create a custom ISO image from my Nix config. This ISO contains a custom install script,
the main advantage being able to use a tool called <a href="https://github.com/diskonauts/disko">disko</a> to partition our disks.
Anyway, I will probably write another post in the future going over how you can do this in another blog post.</p>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">My NixOS Config Explored</summary>
  
  <p>I will also do a more detailed series into my Nix config at some point. This post will be a more general post about
one possible way you can structure your NixOS config.</p>
<p>Heavily inspired by <a href="https://github.com/Misterio77/nix-config">Misterio77</a></p>

</details>

<h2 id="introduction">Introduction</h2>
<p>Currently, we have a single configuration file at <code>/etc/nixos/configuration.nix</code>. However, to edit this file we need sudo
permissions, we also cannot easily put it into a git repository and share this configuration with other machines.
One potential to this solution is to use <a href="https://nixos.org/flake-manual.html">Nix Flakes</a>.</p>
<h2 id="flakes">Flakes</h2>
<p>Nix Flakes exist to improve reproducibility, composability and usability in the Nix ecosystem. What do we mean by that,
well in general they make it easier <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</p>
<ul>
<li>Lock file: They lock all of our dependencies to specific git revisions, so if we try to use the config on another machine it should produce the same &ldquo;outputs&rdquo;</li>
<li>Entry point: The entry point to every nix flake is the <code>flake.nix</code> file, kinda a main function where everything starts from</li>
<li>Share: We can put our flake wherever we want on our system and therefore it is super easy to turn it into a git repo and share it with others</li>
</ul>
<h3 id="getting-started">Getting Started</h3>
<p>So in my case, I did this by creating a new folder in my home directory <code>mkdir $HOME/dotfiles</code>, then going into that
directory <code>cd $HOME/dotfiles</code>, and finally creating a new nix flake <code>nix flake init</code>.</p>
<p>We may need to add the following to our <code>configuration.nix</code>
<code>nix.settings.experimental-features = [ &quot;nix-command&quot; &quot;flakes&quot; ];</code> to allow us to use <code>nix flake</code> command(s).</p>
<p>Now we have a new flake in a git repo. We will now have a <code>flake.nix</code> file which contains three main sections.</p>
<h3 id="inputs">inputs</h3>
<p>Specifies dependencies of this flake, usually other flakes. Usually, I add dependencies you cannot find on <code>nixpkgs</code>,
such as <code>nixvim</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">inputs</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">nixpkgs</span><span class="o">.</span><span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;github:nixos/nixpkgs/nixos-unstable&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">nixvim</span><span class="o">.</span><span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;github:pta2002/nixvim&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>One of the imports that is important is the nixpkgs, this will determine which versions of packages we will get.
Essentially nixpkgs is just a repo full of different nix derivations which tell Nix how to install a package.
Where a Nix derivation is a specific build of a package, which includes all the necessary information, build steps
and dependencies for that package.</p>
<h4 id="flakelock">flake.lock</h4>
<p>In our <code>flake.lock</code> file, we have something like this:</p>
<pre tabindex="0"><code>&#34;nixpkgs_11&#34;: {
  &#34;locked&#34;: {
    &#34;lastModified&#34;: 1693844670,
    &#34;narHash&#34;: &#34;sha256-t69F2nBB8DNQUWHD809oJZJVE+23XBrth4QZuVd6IE0=&#34;,
    &#34;owner&#34;: &#34;nixos&#34;,
    &#34;repo&#34;: &#34;nixpkgs&#34;,
    &#34;rev&#34;: &#34;3c15feef7770eb5500a4b8792623e2d6f598c9c1&#34;,
    &#34;type&#34;: &#34;github&#34;
  },
  &#34;original&#34;: {
    &#34;owner&#34;: &#34;nixos&#34;,
    &#34;ref&#34;: &#34;nixos-unstable&#34;,
    &#34;repo&#34;: &#34;nixpkgs&#34;,
    &#34;type&#34;: &#34;github&#34;
  }
},
</code></pre><p>Where we can see <code>rev</code> is a <a href="https://github.com/NixOS/nixpkgs/commit/3c15feef7770eb5500a4b8792623e2d6f598c9c1">git sha</a>.
In this case, we are looking at a specific branch <code>ref</code>: <code>nixos-unstable</code> so we use the unstable channel,
<a href="https://search.nixos.org/packages?channel=unstable&amp;from=0&amp;size=50&amp;sort=relevance&amp;type=packages&amp;query=ag">https://search.nixos.org/packages?channel=unstable&amp;from=0&amp;size=50&amp;sort=relevance&amp;type=packages&amp;query=ag</a>.</p>
<p>So if we don&rsquo;t ever update our <code>flake.lock</code> we will forever be tied to this version of the unstable channel at that
moment. Of course, that branch is getting updated multiple times a day. So to update our tools/apps etc. we need to update
this lock file. We can do this by running <code>nix flake update</code>, in our dotfiles repo.
The <code>unstable</code> is just a branch on the nixpkgs repo where the packages are updated more often. So when we update
our flakes (using a <code>nix flake update</code>).</p>
<h3 id="outputs">outputs</h3>
<p>The output you can think of it as the different devices we want to configure.
Which includes our actual NixOS config to set up our machine, such as where to backup our files to, and setting up VPNs.
Notice how we are pointing the framework configuration to a configuration file. Which we can build our config using
<code>sudo nixos-rebuild switch --flake ~/dotfiles#framework</code>, using <code>#framework</code> to specify which device to build for.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">outputs</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">nixosConfigurations</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Laptops</span>
</span></span><span class="line"><span class="cl">        <span class="n">framework</span> <span class="o">=</span> <span class="n">lib</span><span class="o">.</span><span class="n">nixosSystem</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">modules</span> <span class="o">=</span> <span class="p">[</span> <span class="sr">./hosts/framework/configuration.nix</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">          <span class="n">specialArgs</span> <span class="o">=</span> <span class="p">{</span> <span class="k">inherit</span> <span class="n">inputs</span> <span class="n">outputs</span><span class="p">;</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h2 id="configuring-nixos">Configuring NixOS</h2>
<p>Now that we have the basic format of what our NixOS config will look like how do we go about actually configuring
our system? We split our config into two main bits. Some key bits of my config, I like as much config to be shared
between my devices as possible but I also want it to be modular, as not every device needs to use every feature.
Some devices don&rsquo;t even run NixOS and only use home-manager.</p>
<h3 id="nixos">NixOS</h3>
<p>The first bit is our NixOS config which we will use to configure our device. Think of anything we need &ldquo;sudo&rdquo; permissions
to do. Again this code block:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">outputs</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">nixosConfigurations</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Laptops</span>
</span></span><span class="line"><span class="cl">        <span class="n">framework</span> <span class="o">=</span> <span class="n">lib</span><span class="o">.</span><span class="n">nixosSystem</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">modules</span> <span class="o">=</span> <span class="p">[</span> <span class="sr">./hosts/framework/configuration.nix</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">          <span class="n">specialArgs</span> <span class="o">=</span> <span class="p">{</span> <span class="k">inherit</span> <span class="n">inputs</span> <span class="n">outputs</span><span class="p">;</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Essentially what we are doing is pointing the flake to use the framework specific <code>configuration.nix</code> file. Then giving
it access to the inputs (and outputs). Which we can access in our configuration.</p>
<h4 id="configurationnix">configuration.nix</h4>
<p>Where the <code>configuration.nix</code> looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span> <span class="n">inputs</span><span class="o">,</span> <span class="o">...</span> <span class="p">}:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">imports</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="n">inputs</span><span class="o">.</span><span class="n">hardware</span><span class="o">.</span><span class="n">nixosModules</span><span class="o">.</span><span class="n">framework-12th-gen-intel</span>
</span></span><span class="line"><span class="cl">    <span class="n">inputs</span><span class="o">.</span><span class="n">hyprland</span><span class="o">.</span><span class="n">nixosModules</span><span class="o">.</span><span class="n">default</span>
</span></span><span class="line"><span class="cl">    <span class="n">inputs</span><span class="o">.</span><span class="n">disko</span><span class="o">.</span><span class="n">nixosModules</span><span class="o">.</span><span class="n">disko</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="sr">./hardware-configuration.nix</span>
</span></span><span class="line"><span class="cl">    <span class="sr">./disks.nix</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="sr">../../nixos/global</span>
</span></span><span class="line"><span class="cl">    <span class="sr">../../nixos/users/haseeb.nix</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="sr">../../nixos/optional/backup.nix</span>
</span></span><span class="line"><span class="cl">    <span class="sr">../../nixos/optional/fingerprint.nix</span>
</span></span><span class="line"><span class="cl">    <span class="sr">../../nixos/optional/docker.nix</span>
</span></span><span class="line"><span class="cl">    <span class="sr">../../nixos/optional/fonts.nix</span>
</span></span><span class="line"><span class="cl">    <span class="sr">../../nixos/optional/pipewire.nix</span>
</span></span><span class="line"><span class="cl">    <span class="sr">../../nixos/optional/greetd.nix</span>
</span></span><span class="line"><span class="cl">    <span class="sr">../../nixos/optional/quietboot.nix</span>
</span></span><span class="line"><span class="cl">    <span class="sr">../../nixos/optional/vfio.nix</span>
</span></span><span class="line"><span class="cl">    <span class="sr">../../nixos/optional/vpn.nix</span>
</span></span><span class="line"><span class="cl">    <span class="sr">../../nixos/optional/pam.nix</span>
</span></span><span class="line"><span class="cl">    <span class="sr">../../nixos/optional/grub.nix</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">networking</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">hostName</span> <span class="o">=</span> <span class="s2">&#34;framework&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">system</span><span class="o">.</span><span class="n">stateVersion</span> <span class="o">=</span> <span class="s2">&#34;23.05&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Some things shared between all of my configs is:</p>
<ul>
<li>inputs (we discussed above)</li>
<li>hardware-configuration</li>
<li>disks (used to partition drives)</li>
</ul>
<h5 id="global">global</h5>
<p>The global config set up the following, which I think I will need in all of my devices. Such as:</p>
<ul>
<li>locale</li>
<li>nix settings</li>
<li>pam auth</li>
<li>opengl</li>
<li>persistence</li>
</ul>
<p>For example, <code>pam.nix</code> looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">security</span><span class="o">.</span><span class="n">pam</span><span class="o">.</span><span class="n">services</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">swaylock</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">u2fAuth</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">login</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">u2fAuth</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">sudo</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">u2fAuth</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Allow us to use a Yubikey to login, unlock Swaylock and to grant sudo permissions.</p>
<h5 id="usershaseebnix">users/haseeb.nix</h5>
<p>Then we decide which users we want to configure on that device, which for now is always <code>haseeb</code> but could change.
Perhaps you want multiple users on a specific device. Where we set up things like:</p>
<ul>
<li>default shell</li>
<li>groups</li>
<li>docker</li>
<li>libvirt</li>
<li>etc &hellip;</li>
<li>home-manager config
<ul>
<li>so a NixOS rebuild also rebuilds the home manager config</li>
</ul>
</li>
<li>hashed password stored using sops-nix (encrypted)</li>
</ul>
<h5 id="optional-features">optional features</h5>
<p>Then alongside the &ldquo;global&rdquo; features, we have a bunch of features/config options which can optionally be turned on by
importing. I think I will likely move to a system I have with my home manager config where you don&rsquo;t turn it on by importing
but by setting an option in an attribute set (you will see this a bit later). However for now you simply import the optional feature.
Which include:</p>
<ul>
<li>backups</li>
<li>fingerprint (not all my devices have fingerprint readers)</li>
<li>enabling thunderbolt</li>
<li>quierboot/grub (could also use systemd-boot)</li>
<li>pipewire</li>
</ul>
<p>You can find a full list of <a href="https://search.nixos.org/options?channel=unstable&amp;from=0&amp;size=50&amp;sort=relevance&amp;type=packages">options here</a></p>
<h2 id="home-manager">Home Manager</h2>
<p>Home Manager is a tool we can use to help configure apps using Nix in our home folder. This includes managing dotfiles.
This can partly be done using nix expressions, used to generate the dotfiles.</p>
<p>This is the main part of my config, which I use to configure my &ldquo;user&rdquo; space. Basically, everything I can do with my user
that doesn&rsquo;t require root permissions <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>. This includes things like:</p>
<ul>
<li>terminal emulator</li>
<li>dotfiles</li>
<li>browsers</li>
<li>editor (nvim)</li>
<li>tmux</li>
</ul>
<p>If I can configure it via home manager I will. You can find a full list of
<a href="https://mipmip.github.io/home-manager-option-search/">home manager options here</a>.</p>
<p>Where the <code>home.nix</code> file acts like a <code>configuration.nix</code> but for home manager.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="o">,</span> <span class="n">pkgs</span>
</span></span><span class="line"><span class="cl"><span class="o">,</span> <span class="n">lib</span>
</span></span><span class="line"><span class="cl"><span class="o">,</span> <span class="n">config</span>
</span></span><span class="line"><span class="cl"><span class="o">,</span> <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">imports</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="sr">../../home-manager</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">config</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">modules</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">browsers</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">firefox</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">editors</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">nvim</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">multiplexers</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">tmux</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">shells</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">fish</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">terminals</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">alacritty</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">foot</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">my</span><span class="o">.</span><span class="n">settings</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">wallpaper</span> <span class="o">=</span> <span class="s2">&#34;~/dotfiles/home-manager/wallpapers/rainbow-nix.jpg&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">host</span> <span class="o">=</span> <span class="s2">&#34;framework&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">default</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">shell</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">fish</span><span class="si">}</span><span class="s2">/bin/fish&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">terminal</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">foot</span><span class="si">}</span><span class="s2">/bin/foot&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">browser</span> <span class="o">=</span> <span class="s2">&#34;firefox&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">editor</span> <span class="o">=</span> <span class="s2">&#34;nvim&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">colorscheme</span> <span class="o">=</span> <span class="n">inputs</span><span class="o">.</span><span class="n">nix-colors</span><span class="o">.</span><span class="n">colorSchemes</span><span class="o">.</span><span class="n">catppuccin-mocha</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">home</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">username</span> <span class="o">=</span> <span class="n">lib</span><span class="o">.</span><span class="n">mkDefault</span> <span class="s2">&#34;haseeb&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">homeDirectory</span> <span class="o">=</span> <span class="n">lib</span><span class="o">.</span><span class="n">mkDefault</span> <span class="s2">&#34;/home/</span><span class="si">${</span><span class="n">config</span><span class="o">.</span><span class="n">home</span><span class="o">.</span><span class="n">username</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">stateVersion</span> <span class="o">=</span> <span class="n">lib</span><span class="o">.</span><span class="n">mkDefault</span> <span class="s2">&#34;23.05&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Here we &ldquo;import&rdquo; all of our options in home-manager and pick and choose what to enable per device:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl">  <span class="n">config</span> <span class="err">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">modules</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">browsers</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">firefox</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">editors</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">nvim</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">multiplexers</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">tmux</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">shells</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">fish</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">terminals</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">alacritty</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">foot</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span></code></pre></div><p>You can see here I enable alacritty and foot terminal managers for this device, so I will have access to both.
Then we also have this which are custom options I have defined. Which will determine the default apps to use.</p>
<p>Where <code>foot.nix</code> looks something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">{</span> <span class="n">config</span><span class="o">,</span> <span class="n">lib</span><span class="o">,</span> <span class="o">...</span> <span class="p">}:</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="n">lib</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">let</span>
</span></span><span class="line"><span class="cl">  <span class="n">cfg</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">modules</span><span class="o">.</span><span class="n">terminals</span><span class="o">.</span><span class="n">foot</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">in</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">options</span><span class="o">.</span><span class="n">modules</span><span class="o">.</span><span class="n">terminals</span><span class="o">.</span><span class="n">foot</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="n">mkEnableOption</span> <span class="s2">&#34;enable foot terminal emulator&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">config</span> <span class="o">=</span> <span class="n">mkIf</span> <span class="n">cfg</span><span class="o">.</span><span class="n">enable</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">programs</span><span class="o">.</span><span class="n">foot</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Here you can see we check if <code>cfg.enable</code> if the foot terminal is enabled then it will be included in our final nix
expression.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl">    <span class="n">my</span><span class="o">.</span><span class="n">settings</span> <span class="err">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">wallpaper</span> <span class="o">=</span> <span class="s2">&#34;~/dotfiles/home-manager/wallpapers/rainbow-nix.jpg&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">host</span> <span class="o">=</span> <span class="s2">&#34;framework&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">default</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">shell</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">fish</span><span class="si">}</span><span class="s2">/bin/fish&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">terminal</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">foot</span><span class="si">}</span><span class="s2">/bin/foot&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">browser</span> <span class="o">=</span> <span class="s2">&#34;firefox&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">editor</span> <span class="o">=</span> <span class="s2">&#34;nvim&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span></code></pre></div><p>Which looks something like allows us to have custom options:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span> <span class="n">lib</span><span class="o">,</span> <span class="n">pkgs</span><span class="o">,</span> <span class="o">...</span> <span class="p">}:</span>
</span></span><span class="line"><span class="cl"><span class="k">let</span>
</span></span><span class="line"><span class="cl">  <span class="k">inherit</span> <span class="p">(</span><span class="n">lib</span><span class="p">)</span> <span class="n">types</span> <span class="n">mkOption</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">in</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">options</span><span class="o">.</span><span class="n">my</span><span class="o">.</span><span class="n">settings</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">default</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">shell</span> <span class="o">=</span> <span class="n">mkOption</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">type</span> <span class="o">=</span> <span class="n">types</span><span class="o">.</span><span class="n">nullOr</span> <span class="p">(</span><span class="n">types</span><span class="o">.</span><span class="n">enum</span> <span class="p">[</span> <span class="s2">&#34;</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">fish</span><span class="si">}</span><span class="s2">/bin/fish&#34;</span> <span class="s2">&#34;</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">zsh</span><span class="si">}</span><span class="s2">/bin/zsh&#34;</span> <span class="p">]);</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span> <span class="o">=</span> <span class="s2">&#34;The default shell to use&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">default</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">fish</span><span class="si">}</span><span class="s2">/bin/fish&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">terminal</span> <span class="o">=</span> <span class="n">mkOption</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">type</span> <span class="o">=</span> <span class="n">types</span><span class="o">.</span><span class="n">nullOr</span> <span class="p">(</span><span class="n">types</span><span class="o">.</span><span class="n">enum</span> <span class="p">[</span> <span class="s2">&#34;alacritty&#34;</span> <span class="s2">&#34;</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">foot</span><span class="si">}</span><span class="s2">/bin/foot&#34;</span> <span class="p">]);</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span> <span class="o">=</span> <span class="s2">&#34;The default terminal to use&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">default</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">foot</span><span class="si">}</span><span class="s2">/bin/foot&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">browser</span> <span class="o">=</span> <span class="n">mkOption</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">type</span> <span class="o">=</span> <span class="n">types</span><span class="o">.</span><span class="n">nullOr</span> <span class="p">(</span><span class="n">types</span><span class="o">.</span><span class="n">enum</span> <span class="p">[</span> <span class="s2">&#34;firefox&#34;</span> <span class="p">]);</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span> <span class="o">=</span> <span class="s2">&#34;The default browser to use&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">default</span> <span class="o">=</span> <span class="s2">&#34;firefox&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">editor</span> <span class="o">=</span> <span class="n">mkOption</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">type</span> <span class="o">=</span> <span class="n">types</span><span class="o">.</span><span class="n">nullOr</span> <span class="p">(</span><span class="n">types</span><span class="o">.</span><span class="n">enum</span> <span class="p">[</span> <span class="s2">&#34;nvim&#34;</span> <span class="s2">&#34;code&#34;</span> <span class="p">]);</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span> <span class="o">=</span> <span class="s2">&#34;The default editor to use&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">default</span> <span class="o">=</span> <span class="s2">&#34;nvim&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>These will then get references in other bits of the config like (take from my sway config):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span><span class="n">config</span><span class="o">,</span> <span class="o">...</span><span class="p">}:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># ....</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;exec </span><span class="si">${</span><span class="n">config</span><span class="o">.</span><span class="n">my</span><span class="o">.</span><span class="n">settings</span><span class="o">.</span><span class="n">default</span><span class="o">.</span><span class="n">browser</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Thats it! We&rsquo;ve gone over how you can setup your NixOS/Nix config, like how I have setup my own!</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/dotfiles">My nix flake</a></li>
</ul>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>A great book about NixOS and flakes https://nixos&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://discourse.nixos.org/t/what-are-the-advantages-of-using-home-manager-with-flake-as-opposed-just-flakes-with-nixos/21628">https://discourse.nixos.org/t/what-are-the-advantages-of-using-home-manager-with-flake-as-opposed-just-flakes-with-nixos/21628</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Fix Networking Issues in a Libvirt VM and Mullvad VPN</title>
      <link>https://haseebmajid.dev/posts/2023-10-21-til-how-to-fix-networking-issues-in-a-libvirt-vm-and-mullvad-vpn/</link>
      <pubDate>Sat, 21 Oct 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-10-21-til-how-to-fix-networking-issues-in-a-libvirt-vm-and-mullvad-vpn/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Fix Networking Issues in a Libvirt VM and Mullvad VPN&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Recently, I was trying to run a libvirt virtual machine, created using &lt;a href=&#34;https://virt-manager.org/&#34;&gt;virt-manager&lt;/a&gt;.
The VM was running NixOS and somehow no matter what I did it would not connect to the internet.&lt;/p&gt;
&lt;p&gt;I was running Mullvad as my VPN, and it turned out this was the app causing issues, I had to turn on
&lt;code&gt;Local network share&lt;/code&gt; &lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. Like in the photo below:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Fix Networking Issues in a Libvirt VM and Mullvad VPN</strong></p>
<p>Recently, I was trying to run a libvirt virtual machine, created using <a href="https://virt-manager.org/">virt-manager</a>.
The VM was running NixOS and somehow no matter what I did it would not connect to the internet.</p>
<p>I was running Mullvad as my VPN, and it turned out this was the app causing issues, I had to turn on
<code>Local network share</code> <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>. Like in the photo below:</p>
<p><img
        loading="lazy"
        src="/posts/2023-10-21-til-how-to-fix-networking-issues-in-a-libvirt-vm-and-mullvad-vpn/images/vpn.png"
        type=""
        alt="VPN"
        
      /></p>
<p>That&rsquo;s It! Simple fix that took me longer to work than I&rsquo;d like to admit.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://mullvad.net/en/help/#29">https://mullvad.net/en/help/#29</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Disable Linters in Golangci Lint for Test Files</title>
      <link>https://haseebmajid.dev/posts/2023-10-10-til-how-to-disable-linters-in-golangci-lint-for-test-files/</link>
      <pubDate>Tue, 10 Oct 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-10-10-til-how-to-disable-linters-in-golangci-lint-for-test-files/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Disable Linters in Golangci Lint for Test Files&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Today (for once), I ran &lt;code&gt;golangci-lint run&lt;/code&gt; and it failed on CI with the following error:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;internal/options/parser_test.go:13: Function &lt;span class=&#34;s1&#34;&gt;&amp;#39;TestParse&amp;#39;&lt;/span&gt; is too long &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;69&lt;/span&gt; &amp;gt; 60&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;funlen&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;func TestParse&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;t *testing.T&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;task: Failed to run task &lt;span class=&#34;s2&#34;&gt;&amp;#34;lint&amp;#34;&lt;/span&gt;: &lt;span class=&#34;nb&#34;&gt;exit&lt;/span&gt; status &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Where my &lt;code&gt;.golangci.yml&lt;/code&gt; file looked like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;timeout&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;5m&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;skip-dirs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;direnv&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;linters&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;enable&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;bodyclose&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;dogsled&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;dupl&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;errcheck&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;exportloopref&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;funlen&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;gochecknoinits&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;goconst&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;gocritic&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;gocyclo&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;gofmt&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;goimports&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;gomnd&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;goprintffuncname&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;gosec&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;gosimple&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;govet&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;ineffassign&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;lll&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;misspell&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;nakedret&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;noctx&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;nolintlint&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;revive&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;staticcheck&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;stylecheck&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;typecheck&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;unconvert&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;unparam&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;unused&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;whitespace&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So basically we want the ability to turn off &lt;code&gt;funlen&lt;/code&gt; for our tests (and other linters). Because we don&amp;rsquo;t mind
if some of our test functions get a big long with different sub-tests.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Disable Linters in Golangci Lint for Test Files</strong></p>
<p>Today (for once), I ran <code>golangci-lint run</code> and it failed on CI with the following error:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">internal/options/parser_test.go:13: Function <span class="s1">&#39;TestParse&#39;</span> is too long <span class="o">(</span><span class="m">69</span> &gt; 60<span class="o">)</span> <span class="o">(</span>funlen<span class="o">)</span>
</span></span><span class="line"><span class="cl">func TestParse<span class="o">(</span>t *testing.T<span class="o">)</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">task: Failed to run task <span class="s2">&#34;lint&#34;</span>: <span class="nb">exit</span> status <span class="m">1</span>
</span></span></code></pre></div><p>Where my <code>.golangci.yml</code> file looked like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">run</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">timeout</span><span class="p">:</span><span class="w"> </span><span class="l">5m</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">skip-dirs</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">direnv</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">linters</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">enable</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">bodyclose</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">dogsled</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">dupl</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">errcheck</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">exportloopref</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">funlen</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">gochecknoinits</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">goconst</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">gocritic</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">gocyclo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">gofmt</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">goimports</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">gomnd</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">goprintffuncname</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">gosec</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">gosimple</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">govet</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">ineffassign</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">lll</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">misspell</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">nakedret</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">noctx</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">nolintlint</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">revive</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">staticcheck</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">stylecheck</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">typecheck</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">unconvert</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">unparam</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">unused</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">whitespace</span><span class="w">
</span></span></span></code></pre></div><p>So basically we want the ability to turn off <code>funlen</code> for our tests (and other linters). Because we don&rsquo;t mind
if some of our test functions get a big long with different sub-tests.</p>
<p>So to disable some linters for our test files we can do something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">linters</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">enable</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">bodyclose</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w"></span><span class="nt">issues</span><span class="p">:</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">  </span><span class="nt">exclude-rules</span><span class="p">:</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">    </span>- <span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">_test.go</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">      </span><span class="nt">linters</span><span class="p">:</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">        </span>- <span class="l">funlen</span><span class="w">
</span></span></span></code></pre></div><p>That&rsquo;s it! For once a TIL, that was written on the same day as I learned it (or re-learnt it)</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Create Systemd Services in Nix Home Manager</title>
      <link>https://haseebmajid.dev/posts/2023-10-08-how-to-create-systemd-services-in-nix-home-manager/</link>
      <pubDate>Sun, 08 Oct 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-10-08-how-to-create-systemd-services-in-nix-home-manager/</guid>
      <description>&lt;p&gt;I recently learnt in home-manager (Nix) you can run systemd services as your own user. This is nice because we don&amp;rsquo;t need
&amp;ldquo;sudo&amp;rdquo; permissions to do so. I also prefer to have as much of my config as possible in home-manager, again I don&amp;rsquo;t
need to run &amp;ldquo;sudo&amp;rdquo;. Which is probably safer running apps in the least privileged mode.&lt;/p&gt;
&lt;p&gt;In my case, I wanted to run an &lt;code&gt;attic&lt;/code&gt;, a binary cache, watch store command which uploads any changes to &lt;code&gt;/nix/store&lt;/code&gt;
to my binary cache. Previously I had this running as a systemd service running as root i.e. managed my NixOS.
However, I wanted to run the same service, on my non-NixOS machine. So I decided to have a look and see if I could
run it in my home-manager config so I could run it the same way across all my machines. Whilst researching I came across
the &lt;code&gt;systemd.user.services&lt;/code&gt; option which allows us to do exactly this.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I recently learnt in home-manager (Nix) you can run systemd services as your own user. This is nice because we don&rsquo;t need
&ldquo;sudo&rdquo; permissions to do so. I also prefer to have as much of my config as possible in home-manager, again I don&rsquo;t
need to run &ldquo;sudo&rdquo;. Which is probably safer running apps in the least privileged mode.</p>
<p>In my case, I wanted to run an <code>attic</code>, a binary cache, watch store command which uploads any changes to <code>/nix/store</code>
to my binary cache. Previously I had this running as a systemd service running as root i.e. managed my NixOS.
However, I wanted to run the same service, on my non-NixOS machine. So I decided to have a look and see if I could
run it in my home-manager config so I could run it the same way across all my machines. Whilst researching I came across
the <code>systemd.user.services</code> option which allows us to do exactly this.</p>
<p>You can see how I set it below:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">systemd</span><span class="o">.</span><span class="n">user</span><span class="o">.</span><span class="n">services</span><span class="o">.</span><span class="n">attic-watch-store</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">Unit</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">Description</span> <span class="o">=</span> <span class="s2">&#34;Push nix store changes to attic binary cache.&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="n">Install</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">WantedBy</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;default.target&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="n">Service</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">ExecStart</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">writeShellScript</span> <span class="s2">&#34;watch-store&#34;</span> <span class="s1">&#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">        #!/run/current-system/sw/bin/bash
</span></span></span><span class="line"><span class="cl"><span class="s1">        ATTIC_TOKEN=$(cat </span><span class="si">${</span><span class="n">config</span><span class="o">.</span><span class="n">sops</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">attic_auth_token</span><span class="o">.</span><span class="n">path</span><span class="si">}</span><span class="s1">)
</span></span></span><span class="line"><span class="cl"><span class="s1">        </span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">attic</span><span class="si">}</span><span class="s1">/bin/attic login prod https://majiy00-nix-binary-cache.fly.dev $ATTIC_TOKEN
</span></span></span><span class="line"><span class="cl"><span class="s1">        </span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">attic</span><span class="si">}</span><span class="s1">/bin/attic use prod
</span></span></span><span class="line"><span class="cl"><span class="s1">        </span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">attic</span><span class="si">}</span><span class="s1">/bin/attic watch-store prod:prod
</span></span></span><span class="line"><span class="cl"><span class="s1">      &#39;&#39;</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>One thing I liked was I didn&rsquo;t need to create an extra binary/shell script for <code>ExecStart</code> to run. i.e.
<code>ExecStart = watch-store.sh</code>. We can simply create a bash script inline and give it a name. home-manager will work out
creating this file and updating the systemd config to point to it for us. One fewer file in our config.</p>
<p>In the example above this is done using the <code>pkgs.writeShellScript</code> function we must provide it with a name (of the file)
i.e. <code>watch-store</code> and then the contents of the file itself.</p>
<p>After running <code>home-manager switch</code> we should be able to see our service running (ignore the fact mine is failing).</p>
<p>The command <code>systemctl --user status attic-watch-store.service</code> could produce the following:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">systemctl --user status attic-watch-store.service
</span></span><span class="line"><span class="cl">× attic-watch-store.service - Push nix store changes to attic binary cache.
</span></span><span class="line"><span class="cl">     Loaded: loaded <span class="o">(</span>/home/haseebmajid/.config/systemd/user/attic-watch-store.service<span class="p">;</span> enabled<span class="p">;</span> vendor preset: enabled<span class="o">)</span>
</span></span><span class="line"><span class="cl">     Active: failed <span class="o">(</span>Result: exit-code<span class="o">)</span> since Sat 2023-10-07 23:38:48 BST<span class="p">;</span> 11h ago
</span></span><span class="line"><span class="cl">    Process: <span class="m">1920392</span> <span class="nv">ExecStart</span><span class="o">=</span>/nix/store/2z87s2lr68c6vwivphv0hp3nscgqfga6-watch-store <span class="o">(</span><span class="nv">code</span><span class="o">=</span>exited, <span class="nv">status</span><span class="o">=</span>1/FAILURE<span class="o">)</span>
</span></span><span class="line"><span class="cl">   Main PID: <span class="m">1920392</span> <span class="o">(</span><span class="nv">code</span><span class="o">=</span>exited, <span class="nv">status</span><span class="o">=</span>1/FAILURE<span class="o">)</span>
</span></span><span class="line"><span class="cl">        CPU: 85ms
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Oct <span class="m">07</span> 23:38:48 nix 2z87s2lr68c6vwivphv0hp3nscgqfga6-watch-store<span class="o">[</span>1920443<span class="o">]</span>:     0: error trying to connect: dns error: Devic&gt;
</span></span><span class="line"><span class="cl">Oct <span class="m">07</span> 23:38:48 nix 2z87s2lr68c6vwivphv0hp3nscgqfga6-watch-store<span class="o">[</span>1920443<span class="o">]</span>:     1: dns error: Device or resource busy <span class="o">(</span>os er&gt;
</span></span><span class="line"><span class="cl">Oct <span class="m">07</span> 23:38:48 nix 2z87s2lr68c6vwivphv0hp3nscgqfga6-watch-store<span class="o">[</span>1920443<span class="o">]</span>:     2: Device or resource busy <span class="o">(</span>os error 16<span class="o">)</span>
</span></span><span class="line"><span class="cl">Oct <span class="m">07</span> 23:38:48 nix 2z87s2lr68c6vwivphv0hp3nscgqfga6-watch-store<span class="o">[</span>1920489<span class="o">]</span>: Error: error sending request <span class="k">for</span> url <span class="o">(</span>https://ma&gt;
</span></span><span class="line"><span class="cl">Oct <span class="m">07</span> 23:38:48 nix 2z87s2lr68c6vwivphv0hp3nscgqfga6-watch-store<span class="o">[</span>1920489<span class="o">]</span>: Caused by:
</span></span><span class="line"><span class="cl">Oct <span class="m">07</span> 23:38:48 nix 2z87s2lr68c6vwivphv0hp3nscgqfga6-watch-store<span class="o">[</span>1920489<span class="o">]</span>:     0: error trying to connect: dns error: faile&gt;
</span></span><span class="line"><span class="cl">Oct <span class="m">07</span> 23:38:48 nix 2z87s2lr68c6vwivphv0hp3nscgqfga6-watch-store<span class="o">[</span>1920489<span class="o">]</span>:     1: dns error: failed to lookup address infor&gt;
</span></span><span class="line"><span class="cl">Oct <span class="m">07</span> 23:38:48 nix 2z87s2lr68c6vwivphv0hp3nscgqfga6-watch-store<span class="o">[</span>1920489<span class="o">]</span>:     2: failed to lookup address information: Tem&gt;
</span></span><span class="line"><span class="cl">Oct <span class="m">07</span> 23:38:48 nix systemd<span class="o">[</span>2551<span class="o">]</span>: attic-watch-store.service: Main process exited, <span class="nv">code</span><span class="o">=</span>exited, <span class="nv">status</span><span class="o">=</span>1/FAILURE
</span></span><span class="line"><span class="cl">Oct <span class="m">07</span> 23:38:48 nix systemd<span class="o">[</span>2551<span class="o">]</span>: attic-watch-store.service: Failed with result <span class="s1">&#39;exit-code&#39;</span>.
</span></span></code></pre></div><p>We can also find the systemd file in <code>~/.config/systemd/user/attic-watch-store.service</code>. Which may look something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">bat attic-watch-store.service --plain
</span></span><span class="line"><span class="cl"><span class="o">[</span>Install<span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="nv">WantedBy</span><span class="o">=</span>default.target
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">[</span>Service<span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="nv">ExecStart</span><span class="o">=</span>/nix/store/2z87s2lr68c6vwivphv0hp3nscgqfga6-watch-store
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">[</span>Unit<span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="nv">Description</span><span class="o">=</span>Push nix store changes to attic binary cache.
</span></span></code></pre></div><p>Where the <code>ExecStart</code> file looks something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">bat /nix/store/2z87s2lr68c6vwivphv0hp3nscgqfga6-watch-store --plain
</span></span><span class="line"><span class="cl"><span class="c1">#!/nix/store/xdqlrixlspkks50m9b0mpvag65m3pf2w-bash-5.2-p15/bin/bash</span>
</span></span><span class="line"><span class="cl"><span class="c1">#!/run/current-system/sw/bin/bash</span>
</span></span><span class="line"><span class="cl"><span class="nv">ATTIC_TOKEN</span><span class="o">=</span><span class="k">$(</span>cat %r/secrets/attic_auth_token<span class="k">)</span>
</span></span><span class="line"><span class="cl">/nix/store/sm7wscbpxv4nsxdv7bik39skll81fy5i-attic-0.1.0/bin/attic login prod https://majiy00-nix-binary-cache.fly.dev <span class="nv">$ATTIC_TOKEN</span>
</span></span><span class="line"><span class="cl">/nix/store/sm7wscbpxv4nsxdv7bik39skll81fy5i-attic-0.1.0/bin/attic use prod
</span></span><span class="line"><span class="cl">/nix/store/sm7wscbpxv4nsxdv7bik39skll81fy5i-attic-0.1.0/bin/attic watch-store prod:prod
</span></span></code></pre></div><p>That&rsquo;s about it! How you can manage systemd services via home-manager running as your own user.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Colour Dap Breakpointed Line in Neovim</title>
      <link>https://haseebmajid.dev/posts/2023-10-07-til-how-to-colour-dap-breakpointed-line-in-neovim/</link>
      <pubDate>Sat, 07 Oct 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-10-07-til-how-to-colour-dap-breakpointed-line-in-neovim/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Colour Dap Breakpointed Line in Neovim&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I wanted to change the background colour of the line when it was stopped during debugging using DAP with neovim.
To make it easier to see where we are currently breakpointed.&lt;/p&gt;
&lt;p&gt;We already have a highlight group called &lt;code&gt;DapStopped&lt;/code&gt;. Which in my case is defined as:
&lt;code&gt;DapStopped = { bg = C.grey }&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Then we need to assign the custom highlight group, the key bit being &lt;code&gt;linehl&lt;/code&gt; for our use case.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Colour Dap Breakpointed Line in Neovim</strong></p>
<p>I wanted to change the background colour of the line when it was stopped during debugging using DAP with neovim.
To make it easier to see where we are currently breakpointed.</p>
<p>We already have a highlight group called <code>DapStopped</code>. Which in my case is defined as:
<code>DapStopped = { bg = C.grey }</code>.</p>
<p>Then we need to assign the custom highlight group, the key bit being <code>linehl</code> for our use case.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="n">require</span><span class="p">(</span><span class="s2">&#34;dap&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">local</span> <span class="n">sign</span> <span class="o">=</span> <span class="n">vim.fn</span><span class="p">.</span><span class="n">sign_define</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">sign</span><span class="p">(</span><span class="s2">&#34;DapBreakpoint&#34;</span><span class="p">,</span> <span class="p">{</span> <span class="n">text</span> <span class="o">=</span> <span class="s2">&#34;●&#34;</span><span class="p">,</span> <span class="n">texthl</span> <span class="o">=</span> <span class="s2">&#34;DapBreakpoint&#34;</span><span class="p">,</span> <span class="n">linehl</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="n">numhl</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="n">sign</span><span class="p">(</span><span class="s2">&#34;DapBreakpointCondition&#34;</span><span class="p">,</span> <span class="p">{</span> <span class="n">text</span> <span class="o">=</span> <span class="s2">&#34;●&#34;</span><span class="p">,</span> <span class="n">texthl</span> <span class="o">=</span> <span class="s2">&#34;DapBreakpointCondition&#34;</span><span class="p">,</span> <span class="n">linehl</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="n">numhl</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="n">sign</span><span class="p">(</span><span class="s2">&#34;DapLogPoint&#34;</span><span class="p">,</span> <span class="p">{</span> <span class="n">text</span> <span class="o">=</span> <span class="s2">&#34;◆&#34;</span><span class="p">,</span> <span class="n">texthl</span> <span class="o">=</span> <span class="s2">&#34;DapLogPoint&#34;</span><span class="p">,</span> <span class="n">linehl</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="n">numhl</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">})</span>
</span></span><span class="line hl"><span class="cl"><span class="n">sign</span><span class="p">(</span><span class="s1">&#39;DapStopped&#39;</span><span class="p">,</span> <span class="p">{</span> <span class="n">text</span><span class="o">=</span><span class="s1">&#39;&#39;</span><span class="p">,</span> <span class="n">texthl</span><span class="o">=</span><span class="s1">&#39;DapStopped&#39;</span><span class="p">,</span> <span class="n">linehl</span><span class="o">=</span><span class="s1">&#39;DapStopped&#39;</span><span class="p">,</span> <span class="n">numhl</span><span class="o">=</span> <span class="s1">&#39;DapStopped&#39;</span> <span class="p">})</span>
</span></span></code></pre></div><p>Then we start the debugger and it stops at a breakpoint line it looks something like in the photo below.</p>
<p><img
        loading="lazy"
        src="/posts/2023-10-07-til-how-to-colour-dap-breakpointed-line-in-neovim/images/debug.png"
        type=""
        alt="Debugging"
        
      /></p>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Fix Package Binary Collisions on Nix</title>
      <link>https://haseebmajid.dev/posts/2023-10-02-til-how-to-fix-package-binary-collisions-on-nix/</link>
      <pubDate>Mon, 02 Oct 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-10-02-til-how-to-fix-package-binary-collisions-on-nix/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Fix Package Binary Collisions on Nix&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Recently I wanted to use &lt;a href=&#34;https://www.gnu.org/software/parallel/&#34;&gt;GNU parallel&lt;/a&gt;, a nifty little tool we can use
to run tasks in parallel, shock horror I know. I already have the &lt;code&gt;moreutils&lt;/code&gt; package installed using nix (home-manager).&lt;/p&gt;
&lt;p&gt;So I added this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;home&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;packages&lt;/span&gt; &lt;span class=&#34;err&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;with&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;pkgs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line hl&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;parallel&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;moreutils&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then I ran the home manager switch like usual, I got the following error.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;error: builder &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;/nix/store/vh6i81xhf6pvybdpall8z8l8y0i6mr8p-home-manager-path.drv&amp;#39;&lt;/span&gt; failed with &lt;span class=&#34;nb&#34;&gt;exit&lt;/span&gt; code 25&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;       last &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; log lines:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;       &amp;gt; error: collision between &lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;/nix/store/slkylri9sbn4w7paaixzc5wj6cwfk83m-moreutils-0.67/bin/parallel&lt;span class=&#34;s1&#34;&gt;&amp;#39; and `/nix/store/fjpnw99zvx2f910s016jrmybc5jxirpn-parallel-20230822/bin/parallel&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;       For full logs, run &lt;span class=&#34;s1&#34;&gt;&amp;#39;nix log /nix/store/vh6i81xhf6pvybdpall8z8l8y0i6mr8p-home-manager-path.drv&amp;#39;&lt;/span&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;error: &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; dependencies of derivation &lt;span class=&#34;s1&#34;&gt;&amp;#39;/nix/store/m4p56x5lgjfpvdy0xjrbk981sfchpl8c-home-manager-generation.drv&amp;#39;&lt;/span&gt; failed to build
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Because &lt;code&gt;moreutils&lt;/code&gt; also have a binary called parallel. How do we tell Nix to overwrite the moreutils binary and use
the one in the &lt;code&gt;parallel&lt;/code&gt; packages?  Simple by using &lt;code&gt;lib.hiPrio&lt;/code&gt; like so:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Fix Package Binary Collisions on Nix</strong></p>
<p>Recently I wanted to use <a href="https://www.gnu.org/software/parallel/">GNU parallel</a>, a nifty little tool we can use
to run tasks in parallel, shock horror I know. I already have the <code>moreutils</code> package installed using nix (home-manager).</p>
<p>So I added this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="n">home</span><span class="o">.</span><span class="n">packages</span> <span class="err">=</span> <span class="k">with</span> <span class="n">pkgs</span><span class="p">;</span> <span class="p">[</span>
</span></span><span class="line hl"><span class="cl">  <span class="n">parallel</span>
</span></span><span class="line"><span class="cl">  <span class="n">moreutils</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span></code></pre></div><p>Then I ran the home manager switch like usual, I got the following error.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">error: builder <span class="k">for</span> <span class="s1">&#39;/nix/store/vh6i81xhf6pvybdpall8z8l8y0i6mr8p-home-manager-path.drv&#39;</span> failed with <span class="nb">exit</span> code 25<span class="p">;</span>
</span></span><span class="line"><span class="cl">       last <span class="m">1</span> log lines:
</span></span><span class="line"><span class="cl">       &gt; error: collision between <span class="sb">`</span>/nix/store/slkylri9sbn4w7paaixzc5wj6cwfk83m-moreutils-0.67/bin/parallel<span class="s1">&#39; and `/nix/store/fjpnw99zvx2f910s016jrmybc5jxirpn-parallel-20230822/bin/parallel&#39;</span>
</span></span><span class="line"><span class="cl">       For full logs, run <span class="s1">&#39;nix log /nix/store/vh6i81xhf6pvybdpall8z8l8y0i6mr8p-home-manager-path.drv&#39;</span>.
</span></span><span class="line"><span class="cl">error: <span class="m">1</span> dependencies of derivation <span class="s1">&#39;/nix/store/m4p56x5lgjfpvdy0xjrbk981sfchpl8c-home-manager-generation.drv&#39;</span> failed to build
</span></span></code></pre></div><p>Because <code>moreutils</code> also have a binary called parallel. How do we tell Nix to overwrite the moreutils binary and use
the one in the <code>parallel</code> packages?  Simple by using <code>lib.hiPrio</code> like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="n">home</span><span class="o">.</span><span class="n">packages</span> <span class="err">=</span> <span class="k">with</span> <span class="n">pkgs</span><span class="p">;</span> <span class="p">[</span>
</span></span><span class="line hl"><span class="cl">  <span class="p">(</span><span class="n">lib</span><span class="o">.</span><span class="n">hiPrio</span> <span class="n">parallel</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">moreutils</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span></code></pre></div><p>This tells Nix to increase the priority of this package you can see
<a href="https://github.com/NixOS/nixpkgs/blob/efdb9b4ee86a3bf3349efa7a23b42cbc18766b90/lib/meta.nix#L52-L69">the code here</a>.</p>
<p>That&rsquo;s it we can still have access to the other binaries moreutils provides like <code>sponge</code> and <code>parallel</code> from parallel
package.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Setup Ventoy on Nixos</title>
      <link>https://haseebmajid.dev/posts/2023-09-29-setup-ventoy-on-nixos/</link>
      <pubDate>Fri, 29 Sep 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-09-29-setup-ventoy-on-nixos/</guid>
      <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Typically when we want to install a new OS we use an ISO to create a bootable USB drive. We can then plug this into
our device, say laptop, and boot from that USB and install our OS. However, typically you can only put a single ISO on
a USB. So what if wanted a USB with say Window, Arch Linux, TailsOS and NixOS how could we do that?&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Typically when we want to install a new OS we use an ISO to create a bootable USB drive. We can then plug this into
our device, say laptop, and boot from that USB and install our OS. However, typically you can only put a single ISO on
a USB. So what if wanted a USB with say Window, Arch Linux, TailsOS and NixOS how could we do that?</p>
<p>I&rsquo;m sure there are other multi-bootable USB tools we could use but I recently came across <a href="https://www.ventoy.net/">ventoy</a>.
All we need to do is format our USB drive with Ventoy, then simply copy our ISO (or other relevant files) onto it.
So we can now have an ISO for windows, arch etc etc. Then we can decide during boot time which ISO ventoy will load
for us.</p>
<p><img
        loading="lazy"
        src="/posts/2023-09-29-setup-ventoy-on-nixos/images/boot.png"
        type=""
        alt="Boot"
        
      /></p>
<h2 id="install-on-nix-nixos">Install on Nix (/NixOS)</h2>
<p>So how do we go about setting it up? Well, I will show you how I set it up on Nix. First plug in a USB that you don&rsquo;t
mind losing all the data on.</p>
<p>If we want to just &ldquo;install&rdquo; ventoy temporarily we can do something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">nix-shell -p ventoy-full
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">sudo ventoy-web
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">===============================================================</span>
</span></span><span class="line"><span class="cl">  Ventoy Server 1.0.95 is running ...
</span></span><span class="line"><span class="cl">  Please open your browser and visit http://127.0.0.1:24680
</span></span><span class="line"><span class="cl"><span class="o">===============================================================</span>
</span></span></code></pre></div><ul>
<li>Open the webpage at <code>http://127.0.0.1:24680</code>,</li>
<li>Select the device you would like to flash</li>
<li>Press the install button</li>
</ul>
<p><img
        loading="lazy"
        src="/posts/2023-09-29-setup-ventoy-on-nixos/images/web.png"
        type=""
        alt="Ventoy Web"
        
      /></p>
<p>After its done you should be able to see a drive called Ventoy and copy over your ISOs to it.</p>
<p><img
        loading="lazy"
        src="/posts/2023-09-29-setup-ventoy-on-nixos/images/files.png"
        type=""
        alt="Copy ISOs to Ventoy"
        
      /></p>
<h2 id="summary">Summary</h2>
<p>That&rsquo;s it! You now have a USB that will allow you to boot to multiple different OS. I originally set this up because
I was creating my own ISO file using Nix flakes. To help automate the installation of new devices (or reinstalling of older ones).</p>
<p>But this is quite cool now because I can have an ISO for my custom Nix image, I can also run TailsOS which gives me
access to Linux on any device. Say, I&rsquo;m at the library, which only has Windows machines. It just avoids the hassle of
either carrying multiple USB devices or constantly formatting and reformatting my USB.</p>
<p>Hope you found this useful!!!</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Use fzf.fish History Search</title>
      <link>https://haseebmajid.dev/posts/2023-09-19-til-how-to-use-fzf-fish-history-search/</link>
      <pubDate>Tue, 19 Sep 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-09-19-til-how-to-use-fzf-fish-history-search/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Use fzf.fish History Search&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I use Fish Shell with fzf and &lt;a href=&#34;https://github.com/PatrickF1/fzf.fish&#34;&gt;fish fzf plugin&lt;/a&gt;.
I installed fzf using nix and home-manager:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;programs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;fzf&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;enable&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;enableFishIntegration&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It adds &lt;code&gt;fzf-history-widget&lt;/code&gt; script, which is bound to &lt;code&gt;ctrl+r&lt;/code&gt;, shell reverse history search.
Which we can see when we run this: &lt;code&gt;bind | grep -e fzf-history-widget -e fzf-file-widget&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Whereas I wanted to use the &lt;code&gt;_fzf_search_history&lt;/code&gt; which is made available by that fish fzf plugin.
Now PatrickF1 recommends running the uninstaller script from fzf, but I found another way we can overwrite
the binding by adding the following lines to our &lt;code&gt;fish.config&lt;/code&gt;:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Use fzf.fish History Search</strong></p>
<p>I use Fish Shell with fzf and <a href="https://github.com/PatrickF1/fzf.fish">fish fzf plugin</a>.
I installed fzf using nix and home-manager:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">programs</span><span class="o">.</span><span class="n">fzf</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">enableFishIntegration</span> <span class="o">=</span> <span class="no">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>It adds <code>fzf-history-widget</code> script, which is bound to <code>ctrl+r</code>, shell reverse history search.
Which we can see when we run this: <code>bind | grep -e fzf-history-widget -e fzf-file-widget</code></p>
<p>Whereas I wanted to use the <code>_fzf_search_history</code> which is made available by that fish fzf plugin.
Now PatrickF1 recommends running the uninstaller script from fzf, but I found another way we can overwrite
the binding by adding the following lines to our <code>fish.config</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fish" data-lang="fish"><span class="line"><span class="cl"><span class="nb">bind</span> <span class="se">\c</span>r _fzf_search_history
</span></span><span class="line"><span class="cl"><span class="nb">bind</span> <span class="na">-M</span> insert <span class="se">\c</span>r _fzf_search_history
</span></span></code></pre></div><p>So now when I press <code>ctrl+r</code> in my shell I used to get this (before):</p>
<p><img
        loading="lazy"
        src="/posts/2023-09-19-til-how-to-use-fzf-fish-history-search/images/fzf_original.png"
        type=""
        alt="FZF"
        
      /></p>
<p>but now I get this (after):</p>
<p><img
        loading="lazy"
        src="/posts/2023-09-19-til-how-to-use-fzf-fish-history-search/images/fish_fzf.png"
        type=""
        alt="FZF Fish"
        
      /></p>
<h2 id="appendix">Appendix</h2>
<ul>
<li>[My nix fish shell config](<a href="https://gitlab.com/hmaji%5D(https://gitlab.com/hmajid2301/dotfiles/-/blob/a34417ed452fe068ec4a91fba7ef0a14fca31b76/home-manager/shells/fish.nix#L48-49)">https://gitlab.com/hmaji](https://gitlab.com/hmajid2301/dotfiles/-/blob/a34417ed452fe068ec4a91fba7ef0a14fca31b76/home-manager/shells/fish.nix#L48-49)</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>My NixOS Dotfiles Explained</title>
      <link>https://haseebmajid.dev/posts/2023-09-12-how-i-configure-nixos-as-part-of-my-development-workflow/</link>
      <pubDate>Tue, 12 Sep 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-09-12-how-i-configure-nixos-as-part-of-my-development-workflow/</guid>
      <description>&lt;p&gt;In this post, we will just go over the basics of how we can configure our setup using a git repo, nix flakes and
home-manager. I will go over how I structured my nix config.&lt;/p&gt;
&lt;details
  class=&#34;notice Info&#34;
  open=&#34;true&#34;
&gt;
    &lt;summary class=&#34;notice-title&#34;&gt;Not an in-depth tutorial&lt;/summary&gt;
  
  Note this will not be an in-depth guide into NixOS/Home Manager itself. That could well be a series on its own.
We will just go over the main ways I configure NixOS/Nix and why I do it the way I do. I recommend doing some reading
and playing around and figuring out what works for you &amp;#x1f604;
&lt;/details&gt;

&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;We will be using two different ways to configure our system, NixOS via a &lt;code&gt;configuration.nix&lt;/code&gt; to configure the machine
itself. Which includes partitions, backups, docker anything that needs to be run system-wide. Anything that
needs &amp;ldquo;sudo&amp;rdquo; permissions is configured via this expression. Note we will of course split our nix expressions into smaller
modules. That will be re-used between multiple hosts.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this post, we will just go over the basics of how we can configure our setup using a git repo, nix flakes and
home-manager. I will go over how I structured my nix config.</p>
<details
  class="notice Info"
  open="true"
>
    <summary class="notice-title">Not an in-depth tutorial</summary>
  
  Note this will not be an in-depth guide into NixOS/Home Manager itself. That could well be a series on its own.
We will just go over the main ways I configure NixOS/Nix and why I do it the way I do. I recommend doing some reading
and playing around and figuring out what works for you &#x1f604;
</details>

<h2 id="introduction">Introduction</h2>
<p>We will be using two different ways to configure our system, NixOS via a <code>configuration.nix</code> to configure the machine
itself. Which includes partitions, backups, docker anything that needs to be run system-wide. Anything that
needs &ldquo;sudo&rdquo; permissions is configured via this expression. Note we will of course split our nix expressions into smaller
modules. That will be re-used between multiple hosts.</p>
<p>The second way we will configure our system (most of our config in fact) will be <code>home-manager</code>. This is a Nix tool
to configure things in userland. Such as dotfiles, and most of my apps. So for example here is where I define my neovim
config, and various cli tools I use (like zoxide, fzf). [^1]</p>
<h2 id="git-repo">Git Repo</h2>
<p>Typically NixOS can be configured using a file at <code>/etc/nixos/configuration.nix</code>, however, to edit this file you need
sudo permissions. We also cannot easily track this file in Git. One of the main benefits of using NixOS/Nix is that
we can use declaratively define our machine state, store that in a git repo and then re-use that across multiple devices.</p>
<p>Here is my <a href="https://gitlab.com/hmajid2301/dotfiles">git repo</a>, when setting up my host mesmer it probably took about
20 minutes to setup my entire machine. Most of which was spent downloading, building, and evaluating nix derivations.</p>
<h3 id="flakes">Flakes</h3>
<p>We will also use Nix flakes alongside our git repo.
I won&rsquo;t go into lots of details of what flakes are [^2]. But nix flakes seem to be the preferred way to define our
nix configs. Flakes improve reproducibility by creating a lock file for our dependencies and also creating a project
so it&rsquo;s easier for new people to navigate our Nix config.</p>
<p>A <code>flake.nix</code> file becomes a bit like <code>package.json</code> file in javascript land. Alongside this flake file, a lock file
will be generated. Which will lock our dependencies to specific values. So if anyone else uses our flake they will get
the same versions of the nix derivations that we have. This includes nixpkgs, so even when these are updated we tie
them to a specific version.</p>
<p>Typically when we start using flakes initially they also go in <code>/etc/nixos/flake.nix</code>. Which causes similar problems
as the configuration file. However, this is an easyish way to create a git repo in say <code>~/dotfiles</code> which is a flake
and can be used to configure both our system and home-manager.</p>
<details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">Experimental</summary>
  
  Whilst lots of people in the Nix community have already adopted flakes. It&rsquo;s important to remember they are still an
experimental feature and are likely to be the cause of breaking changes in the future. So use it with caution if you
are worried about this sort of thing.
</details>

<h2 id="getting-started">Getting Started</h2>
<p>So we want to use a git repo and flakes to configure our system. Even before using Nix/NixOS, I was using a dotfiles repo.
Which would be located in my home directory i.e <code>~/dotfiles</code>. So I kept the same location for my nix dotfiles as well.</p>
<p>Then to enable flakes let&rsquo;s add the following to <code>nix.settings.experimental-features = [ &quot;nix-command&quot; &quot;flakes&quot; ];</code>
to our <code>configuration.nix</code> file. Then run <code>sudo nixos-rebuild switch</code> to rebuild our NixOS config.</p>
<h3 id="repo-structure">Repo Structure</h3>
<p>There are 100 different ways to structure our dotfiles repo. I ended up using this
<a href="https://github.com/Misterio77/nix-starter-configs">starter repo (standard version)</a> and used
<a href="https://github.com/Misterio77/nix-config">Misterio77&rsquo;s main repo</a> for inspiration.</p>
<p>Where my repo looks something like this (simplified):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">.
</span></span><span class="line"><span class="cl">├── flake.lock
</span></span><span class="line"><span class="cl">├── flake.nix
</span></span><span class="line"><span class="cl">├── home-manager
</span></span><span class="line"><span class="cl">├── hosts
</span></span><span class="line"><span class="cl">│   ├── curve
</span></span><span class="line"><span class="cl">│   │   └── home.nix
</span></span><span class="line"><span class="cl">│   ├── framework
</span></span><span class="line"><span class="cl">│   │   ├── configuration.nix
</span></span><span class="line"><span class="cl">│   │   ├── hardware-configuration.nix
</span></span><span class="line"><span class="cl">│   │   ├── home.nix
</span></span><span class="line"><span class="cl">│   │   ├── secrets.yaml
</span></span><span class="line"><span class="cl">│   │   └── users
</span></span><span class="line"><span class="cl">│   └── mesmer
</span></span><span class="line"><span class="cl">│       ├── configuration.nix
</span></span><span class="line"><span class="cl">│       ├── hardware-configuration.nix
</span></span><span class="line"><span class="cl">│       ├── home.nix
</span></span><span class="line"><span class="cl">│       ├── secrets.yaml
</span></span><span class="line"><span class="cl">│       └── users
</span></span><span class="line"><span class="cl">├── LICENSE.md
</span></span><span class="line"><span class="cl">├── modules
</span></span><span class="line"><span class="cl">├── nixos
</span></span><span class="line"><span class="cl">├── overlays
</span></span><span class="line"><span class="cl">├── pkgs
</span></span><span class="line"><span class="cl">├── README.md
</span></span><span class="line"><span class="cl">└── shell.nix
</span></span></code></pre></div><p>Some key bits are:</p>
<ul>
<li><code>flake.nix</code>: The entry point into the dotfiles, defines all of our NixOS hosts and home-manager hosts, inputs etc</li>
<li><code>nixos</code>: Has most of the re-usable NixOS expressions</li>
<li><code>home-manager</code>: Has most of the re-usable home manager nix expressions</li>
<li><code>hosts</code>: After <code>flake.nix</code> these are the secondary entry points into the configuration for each host</li>
</ul>
<h4 id="flakenix">flake.nix</h4>
<p>Here is a simplified version of my <code>flake.nix</code> file:</p>
<pre tabindex="0"><code>{
  description = &#34;My Nix Config&#34;;

  nixConfig = {
    experimental-features = [ &#34;nix-command&#34; &#34;flakes&#34; ];
  };

  inputs = {
    nixpkgs.url = &#34;github:nixos/nixpkgs/nixos-unstable&#34;;
    home-manager.url = &#34;github:nix-community/home-manager&#34;;
  };

  outputs =
    { self
    , nixpkgs
    , home-manager
    , ...
    } @ inputs:
    {
      nixosConfigurations = {
        # Personal laptop
        framework = lib.nixosSystem {
          modules = [ ./hosts/framework/configuration.nix ];
          specialArgs = { inherit inputs outputs; };
        };
      };

      homeConfigurations = {
        # Laptops
        framework = lib.homeManagerConfiguration {
          modules = [ ./hosts/framework/home.nix ];
          pkgs = nixpkgs.legacyPackages.x86_64-linux;
          extraSpecialArgs = { inherit inputs outputs; };
        };

        curve = lib.homeManagerConfiguration {
          modules = [ ./hosts/curve/home.nix ];
          pkgs = nixpkgs.legacyPackages.x86_64-linux;
          extraSpecialArgs = { inherit inputs outputs; };
        };
      };
    };
}
</code></pre><p>We have:</p>
<ul>
<li><code>description</code>: For what the flake is, not important</li>
<li><code>inputs</code>: Other nix flakes I&rsquo;m using as imports, such as home-manager to install the home-manager binary and nixpkgs to use unstable</li>
<li><code>output</code>: Which contains two main bits
<ul>
<li><code>nixosConfiguration</code>: Uses <code>configuration.nix</code> as an entry point (in a specific host file)</li>
<li><code>homeManagerConfiguration</code>: Uses <code>home.nix</code> as an entry point (in specific host file)
<ul>
<li><code>curve</code>: You will notice this host only has home manager config because it is running Ubuntu so only uses home manager, which to be honest is good enough in my opinion</li>
</ul>
</li>
</ul>
</li>
</ul>
<h5 id="flakelock">flake.lock</h5>
<p>In our <code>flake.lock</code> file, we have something like this:</p>
<pre tabindex="0"><code>&#34;nixpkgs_11&#34;: {
  &#34;locked&#34;: {
    &#34;lastModified&#34;: 1693844670,
    &#34;narHash&#34;: &#34;sha256-t69F2nBB8DNQUWHD809oJZJVE+23XBrth4QZuVd6IE0=&#34;,
    &#34;owner&#34;: &#34;nixos&#34;,
    &#34;repo&#34;: &#34;nixpkgs&#34;,
    &#34;rev&#34;: &#34;3c15feef7770eb5500a4b8792623e2d6f598c9c1&#34;,
    &#34;type&#34;: &#34;github&#34;
  },
  &#34;original&#34;: {
    &#34;owner&#34;: &#34;nixos&#34;,
    &#34;ref&#34;: &#34;nixos-unstable&#34;,
    &#34;repo&#34;: &#34;nixpkgs&#34;,
    &#34;type&#34;: &#34;github&#34;
  }
},
</code></pre><p>Where we can see <code>rev</code> is a <a href="https://github.com/NixOS/nixpkgs/commit/3c15feef7770eb5500a4b8792623e2d6f598c9c1">git sha</a>.
In this case, we are looking at a specific branch <code>ref</code>: <code>nixos-unstable</code> so we use the unstable channel,
<a href="https://search.nixos.org/packages?channel=unstable&amp;from=0&amp;size=50&amp;sort=relevance&amp;type=packages&amp;query=ag">https://search.nixos.org/packages?channel=unstable&amp;from=0&amp;size=50&amp;sort=relevance&amp;type=packages&amp;query=ag</a>.</p>
<p>So if we don&rsquo;t ever update our <code>flake.lock</code> we will forever be tied to this version of the unstable channel at that
moment. Of course, that branch is getting updated multiple times a day. So to update our tools/apps etc. we need to update
this lock file. We can do this by running <code>nix flake update</code>, in our dotfiles repo.</p>
<h4 id="nixos">NixOS</h4>
<p>Let&rsquo;s take a look at how we configure a device using NixOS, so looking at my <code>hosts/framework/configuration.nix</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span> <span class="n">inputs</span>
</span></span><span class="line"><span class="cl"><span class="o">,</span> <span class="n">pkgs</span>
</span></span><span class="line"><span class="cl"><span class="o">,</span> <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">imports</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="n">inputs</span><span class="o">.</span><span class="n">hardware</span><span class="o">.</span><span class="n">nixosModules</span><span class="o">.</span><span class="n">framework-12th-gen-intel</span>
</span></span><span class="line"><span class="cl">    <span class="n">inputs</span><span class="o">.</span><span class="n">nix-gaming</span><span class="o">.</span><span class="n">nixosModules</span><span class="o">.</span><span class="n">default</span>
</span></span><span class="line"><span class="cl">    <span class="n">inputs</span><span class="o">.</span><span class="n">hyprland</span><span class="o">.</span><span class="n">nixosModules</span><span class="o">.</span><span class="n">default</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="sr">./hardware-configuration.nix</span>
</span></span><span class="line"><span class="cl">    <span class="sr">./users/haseeb</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="sr">../../nixos/global</span>
</span></span><span class="line"><span class="cl">    <span class="sr">../../nixos/optional/backup.nix</span>
</span></span><span class="line"><span class="cl">    <span class="sr">../../nixos/optional/greetd.nix</span>
</span></span><span class="line"><span class="cl">    <span class="sr">../../nixos/optional/mullvad.nix</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Enable networking</span>
</span></span><span class="line"><span class="cl">  <span class="n">networking</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">networkmanager</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="n">hostName</span> <span class="o">=</span> <span class="s2">&#34;framework&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">boot</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">loader</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">systemd-boot</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">efi</span><span class="o">.</span><span class="n">canTouchEfiVariables</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="n">initrd</span><span class="o">.</span><span class="n">luks</span><span class="o">.</span><span class="n">devices</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">root</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">device</span> <span class="o">=</span> <span class="s2">&#34;/dev/disk/by-uuid/fc112246-8ce0-47c7-95e5-106be34e9501&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">preLVM</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="n">kernelPackages</span> <span class="o">=</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">linuxPackages_latest</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">system</span><span class="o">.</span><span class="n">stateVersion</span> <span class="o">=</span> <span class="s2">&#34;23.05&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The main thing this acts as an entry point for our NixOS config (system-wide). This allows us to specify which packages we
want to on different devices. Essentially all the imports are re-usable nix expressions, that can be shared across
devices.</p>
<p>Taking a look at an optional expression say <code>greetd.nix</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">services</span><span class="o">.</span><span class="n">greetd</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">settings</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">initial_session</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">command</span> <span class="o">=</span> <span class="s2">&#34;Hyprland&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">user</span> <span class="o">=</span> <span class="s2">&#34;haseeb&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">      <span class="n">default_session</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">command</span> <span class="o">=</span> <span class="s2">&#34;initial_session&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="n">environment</span><span class="o">.</span><span class="n">etc</span><span class="o">.</span><span class="s2">&#34;greetd/environments&#34;</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="s1">&#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">    Hyprland
</span></span></span><span class="line"><span class="cl"><span class="s1">  &#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Here you can see we define a greetd service to run, which will run Hyprland when we log in (and which user to log us into).</p>
<p>Where we can find a full list of <a href="https://search.nixos.org/options?channel=unstable&amp;from=0&amp;size=50&amp;sort=relevance&amp;type=packages&amp;query=greetd">options here</a>.</p>
<h4 id="home-manager">Home Manager</h4>
<p>Similar to the above section, the <code>hosts/framework/home.nix</code> acts as the entry point to our home-manager config.
Here is where most of my nix expression/config lies. This configures the home directory of a user. But can also be used
to install and configure most of our tools and apps. Let&rsquo;s take a look at an example file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span> <span class="n">inputs</span>
</span></span><span class="line"><span class="cl"><span class="o">,</span> <span class="n">lib</span>
</span></span><span class="line"><span class="cl"><span class="o">,</span> <span class="n">pkgs</span>
</span></span><span class="line"><span class="cl"><span class="o">,</span> <span class="n">config</span>
</span></span><span class="line"><span class="cl"><span class="o">,</span> <span class="n">outputs</span>
</span></span><span class="line"><span class="cl"><span class="o">,</span> <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">imports</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl">    <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="n">inputs</span><span class="o">.</span><span class="n">nix-colors</span><span class="o">.</span><span class="n">homeManagerModule</span>
</span></span><span class="line"><span class="cl">      <span class="n">inputs</span><span class="o">.</span><span class="n">nixvim</span><span class="o">.</span><span class="n">homeManagerModules</span><span class="o">.</span><span class="n">nixvim</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="sr">../../home-manager/desktops/hyprland</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="sr">../../home-manager/shells/fish.nix</span>
</span></span><span class="line"><span class="cl">      <span class="sr">../../home-manager/terminals/foot.nix</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="sr">../../home-manager/browsers/firefox.nix</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="sr">../../home-manager/programs/cli</span>
</span></span><span class="line"><span class="cl">      <span class="sr">../../home-manager/editors/nvim</span>
</span></span><span class="line"><span class="cl">      <span class="sr">../../home-manager/programs/multiplexers/tmux.nix</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="sr">../../home-manager/games</span>
</span></span><span class="line"><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">colorscheme</span> <span class="o">=</span> <span class="n">inputs</span><span class="o">.</span><span class="n">nix-colors</span><span class="o">.</span><span class="n">colorSchemes</span><span class="o">.</span><span class="n">catppuccin-frappe</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">wallpaper</span> <span class="o">=</span> <span class="s2">&#34;~/dotfiles/home-manager/wallpapers/rainbow-nix.jpg&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">host</span> <span class="o">=</span> <span class="s2">&#34;framework&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">nix</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">package</span> <span class="o">=</span> <span class="n">lib</span><span class="o">.</span><span class="n">mkDefault</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">nix</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">settings</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">experimental-features</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;nix-command&#34;</span> <span class="s2">&#34;flakes&#34;</span> <span class="s2">&#34;repl-flake&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">      <span class="n">warn-dirty</span> <span class="o">=</span> <span class="no">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">systemd</span><span class="o">.</span><span class="n">user</span><span class="o">.</span><span class="n">startServices</span> <span class="o">=</span> <span class="s2">&#34;sd-switch&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">programs</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">home-manager</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">git</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">home</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">username</span> <span class="o">=</span> <span class="n">lib</span><span class="o">.</span><span class="n">mkDefault</span> <span class="s2">&#34;haseeb&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">homeDirectory</span> <span class="o">=</span> <span class="n">lib</span><span class="o">.</span><span class="n">mkDefault</span> <span class="s2">&#34;/home/</span><span class="si">${</span><span class="n">config</span><span class="o">.</span><span class="n">home</span><span class="o">.</span><span class="n">username</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">stateVersion</span> <span class="o">=</span> <span class="n">lib</span><span class="o">.</span><span class="n">mkDefault</span> <span class="s2">&#34;23.05&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">sessionPath</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;$HOME/.local/bin&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="n">sessionVariables</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">TERMINAL</span> <span class="o">=</span> <span class="s2">&#34;alacritty&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">EDITOR</span> <span class="o">=</span> <span class="s2">&#34;nvim&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">BROWSER</span> <span class="o">=</span> <span class="s2">&#34;firefox&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>You can see the example looks very similar to our <code>configuration.nix</code> file. I try to configure my tooling in home-manager
first, if I can. We don&rsquo;t need sudo permissions to do this. The more that lives in my home directory the easier is it to backup.
For example, we can take a look my <code>programs/fzf.nix</code> expression:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">programs</span><span class="o">.</span><span class="n">fzf</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">enableFishIntegration</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>We can find a set of <a href="https://mipmip.github.io/home-manager-option-search/?query=fzf">home-manager options here</a>.
Not everything is here (especially nixvim).</p>
<p>That&rsquo;s It! I didn&rsquo;t go into lots of specifics in this article but more a general view of how I go about configuring
my system. Hopefully, it made it more clear how I use NixOS. And even a bit of Nix on non-NixOS-based machines.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/dotfiles/-/tree/3e35e17bab99518ba4136c552a9de790daf9ae0d">My dotfiles</a></li>
<li><a href="https://github.com/Misterio77/nix-config">Misterio77&rsquo;s Repo</a></li>
<li><a href="https://mipmip.github.io/home-manager-option-search/?query=fzf">home-manager options super useful</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Check if Another Option Is Set in Home Manager (Nix)</title>
      <link>https://haseebmajid.dev/posts/2023-09-10-til-how-to-check-if-another-option-is-set-in-home-manager-nix/</link>
      <pubDate>Sun, 10 Sep 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-09-10-til-how-to-check-if-another-option-is-set-in-home-manager-nix/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Check if Another Option Is Set in Home Manager (Nix)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Recently I was adding sway to my nix config (setup via home-manager). I already had Hyprland config, I wanted both
sway and Hyprland to use my waybar config with some slight differences. So basically I want to check if the current
host machine is using Sway or Hyprland (I am assuming we will only use one).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The main reason for using Sway is my work laptop uses Ubuntu 22.04, It&amp;rsquo;s not easy to run Hyprland on a &amp;ldquo;stable&amp;rdquo; distro like Ubuntu better suited to Arch or NixOS.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Check if Another Option Is Set in Home Manager (Nix)</strong></p>
<p>Recently I was adding sway to my nix config (setup via home-manager). I already had Hyprland config, I wanted both
sway and Hyprland to use my waybar config with some slight differences. So basically I want to check if the current
host machine is using Sway or Hyprland (I am assuming we will only use one).</p>
<blockquote>
<p>The main reason for using Sway is my work laptop uses Ubuntu 22.04, It&rsquo;s not easy to run Hyprland on a &ldquo;stable&rdquo; distro like Ubuntu better suited to Arch or NixOS.</p>
</blockquote>
<p>If sway is enabled include some specific config in my Waybar config else include the Hyprland config. So first of all
I have a file called <code>sway.nix</code> which looks something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">wayland</span><span class="o">.</span><span class="n">windowManager</span><span class="o">.</span><span class="n">sway</span> <span class="err">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">package</span> <span class="o">=</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">swayfx</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>I then have a host file for my work laptop called <code>curve/home.nix</code> which imports this sway expression:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="n">imports</span> <span class="err">=</span> <span class="p">[</span> <span class="sr">../../home-manager/desktops/sway.nix</span> <span class="p">];</span>
</span></span></code></pre></div><p>Finally, the main part of this article, let&rsquo;s look at my <code>waybar.nix</code> which is imported by <code>sway.nix</code> (and <code>hyprland.nix</code>) expression, which
Check if sway is enabled (which happens if the <code>sway.nix</code> is imported).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span> <span class="n">config</span><span class="o">,</span> <span class="o">...</span> <span class="p">}:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">programs</span><span class="o">.</span><span class="n">waybar</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">settings</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">modules-left</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">          <span class="s2">&#34;custom/launcher&#34;</span>
</span></span><span class="line hl"><span class="cl">          <span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="n">config</span><span class="o">.</span><span class="n">wayland</span><span class="o">.</span><span class="n">windowManager</span><span class="o">.</span><span class="n">sway</span><span class="o">.</span><span class="n">enable</span> <span class="o">==</span> <span class="no">true</span>
</span></span><span class="line"><span class="cl">            <span class="k">then</span> <span class="s2">&#34;sway/workspaces&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="k">else</span> <span class="s2">&#34;hyprland/workspaces&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="p">)</span>
</span></span><span class="line"><span class="cl">          <span class="s2">&#34;custom/currentplayer&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="s2">&#34;custom/player&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="s2">&#34;custom/audio_idle_inhibitor&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="err">}</span>
</span></span><span class="line"><span class="cl"><span class="err">}</span>
</span></span></code></pre></div><p>In the example above <code>config.wayland.windowManager.sway.enable == true</code>, we just do a simple if statement check to
Decide which Waybar module to show (sway or Hyprland).</p>
<p>That&rsquo;s it! How we can check if other expressions can use attributes set in other expression files.
If you have suggestions to make this cleaner I&rsquo;m all ears! This is not cleanest way to do something like this.
But since these are just my own dotfiles I know I will only use one of Sway or Hyprland on my devices.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Access System in Home Manager Using Flakes</title>
      <link>https://haseebmajid.dev/posts/2023-09-05-til-how-to-access-system-in-home-manager-using-flakes/</link>
      <pubDate>Tue, 05 Sep 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-09-05-til-how-to-access-system-in-home-manager-using-flakes/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Access System in Home Manager Using Flakes&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Recently I needed to install devenv using flakes in home-manager. One of the things I needed to pass to was the type
of system to install on i.e. &lt;code&gt;&amp;quot;x86_64-linux&amp;quot;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So as I temp hack I had something like: &lt;code&gt;inputs.devenv.packages.&amp;quot;x86_64-linux&amp;quot;.devenv&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;However I was able to access the system using the &lt;code&gt;pkgs&lt;/code&gt; attribute like so:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;inputs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;pkgs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;o&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;home&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;packages&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;inputs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;devenv&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;packages&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;pkgs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;system&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;devenv&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Where my &lt;code&gt;flake.nix&lt;/code&gt; looks something like:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Access System in Home Manager Using Flakes</strong></p>
<p>Recently I needed to install devenv using flakes in home-manager. One of the things I needed to pass to was the type
of system to install on i.e. <code>&quot;x86_64-linux&quot;</code>.</p>
<p>So as I temp hack I had something like: <code>inputs.devenv.packages.&quot;x86_64-linux&quot;.devenv</code>.</p>
<p>However I was able to access the system using the <code>pkgs</code> attribute like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">inputs</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">pkgs</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">home</span><span class="o">.</span><span class="n">packages</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="n">inputs</span><span class="o">.</span><span class="n">devenv</span><span class="o">.</span><span class="n">packages</span><span class="o">.</span><span class="s2">&#34;</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">system</span><span class="si">}</span><span class="s2">&#34;</span><span class="o">.</span><span class="n">devenv</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Where my <code>flake.nix</code> looks something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl">  <span class="n">outputs</span> <span class="err">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">self</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">nixpkgs</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">home-manager</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="o">@</span> <span class="n">inputs</span><span class="p">:</span> <span class="k">let</span>
</span></span><span class="line"><span class="cl">  <span class="k">in</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">inherit</span> <span class="n">lib</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">homeConfigurations</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="c1"># Desktops</span>
</span></span><span class="line"><span class="cl">      <span class="n">mesmer</span> <span class="o">=</span> <span class="n">lib</span><span class="o">.</span><span class="n">homeManagerConfiguration</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">modules</span> <span class="o">=</span> <span class="p">[</span><span class="sr">./hosts/mesmer/home.nix</span><span class="p">];</span>
</span></span><span class="line hl"><span class="cl">        <span class="n">pkgs</span> <span class="o">=</span> <span class="n">nixpkgs</span><span class="o">.</span><span class="n">legacyPackages</span><span class="o">.</span><span class="n">x86_64-linux</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">extraSpecialArgs</span> <span class="o">=</span> <span class="p">{</span><span class="k">inherit</span> <span class="n">inputs</span> <span class="n">outputs</span><span class="p">;};</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span></code></pre></div><p>You can see here the system is defined with the pkgs, as I guess we need to know for which architecture we should
build for i.e. amd, arch etc. But yeh thats it! For use within home-manager it seems we can use <code>pkgs.system</code>, to
access the architecture, so we don&rsquo;t need to hardcode it.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Fix tmux-resurrect on NixOS</title>
      <link>https://haseebmajid.dev/posts/2023-09-01-til-how-to-fix-tmux-resurrect-on-nixos/</link>
      <pubDate>Fri, 01 Sep 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-09-01-til-how-to-fix-tmux-resurrect-on-nixos/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Fix tmux-resurrect on NixOS&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When I moved to NixOS I noticed that &lt;a href=&#34;https://github.com/tmux-plugins/tmux-resurrect&#34;&gt; tmux-resurrect &lt;/a&gt; stop restoring
some applications such as &lt;code&gt;man&lt;/code&gt; and &lt;code&gt;nvim&lt;/code&gt;. Like it used to on my Arch machine. I recently found a solution to my
problem (thanks to a lovely chap on the nixos discourse).&lt;/p&gt;
&lt;p&gt;By adding the following lines to our tmux config:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-tmux&#34; data-lang=&#34;tmux&#34;&gt;resurrect_dir=&amp;#34;$HOME/.tmux/resurrect&amp;#34;
set -g @resurrect-dir $resurrect_dir
set -g @resurrect-hook-post-save-all &amp;#39;target=$(readlink -f $resurrect_dir/last); sed &amp;#34;s| --cmd .*-vim-pack-dir||g; s|/etc/profiles/per-user/$USER/bin/||g; s|/home/$USER/.nix-profile/bin/||g&amp;#34; $target | sponge $target&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;What this does is it edits the tmux-resurrect file from this:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Fix tmux-resurrect on NixOS</strong></p>
<p>When I moved to NixOS I noticed that <a href="https://github.com/tmux-plugins/tmux-resurrect"> tmux-resurrect </a> stop restoring
some applications such as <code>man</code> and <code>nvim</code>. Like it used to on my Arch machine. I recently found a solution to my
problem (thanks to a lovely chap on the nixos discourse).</p>
<p>By adding the following lines to our tmux config:</p>
<pre tabindex="0"><code class="language-tmux" data-lang="tmux">resurrect_dir=&#34;$HOME/.tmux/resurrect&#34;
set -g @resurrect-dir $resurrect_dir
set -g @resurrect-hook-post-save-all &#39;target=$(readlink -f $resurrect_dir/last); sed &#34;s| --cmd .*-vim-pack-dir||g; s|/etc/profiles/per-user/$USER/bin/||g; s|/home/$USER/.nix-profile/bin/||g&#34; $target | sponge $target&#39;
</code></pre><p>What this does is it edits the tmux-resurrect file from this:</p>
<pre tabindex="0"><code># bat ~/.tmux/resurrect/last --plain

pane    dotfiles    0   1   :*  0   nvim ~/dotfiles :/home/haseeb/dotfiles  1   nvim    :/home/haseeb/.nix-profile/bin/nvim --cmd lua vim.g.loaded_node_provider=0;vim.g.loaded_perl_provider=0;vim.g.loaded_python_provider=0;vim.g.python3_host_prog=&#39;/nix/store/4n4d0f1xd14gl4pfymdcqb9pmagcyyfj-neovim-0.9.1/bin/nvim-python3&#39;;vim.g.ruby_host_prog=&#39;/nix/store/4n4d0f1xd14gl4pfymdcqb9pmagcyyfj-neovim-0.9.1/bin/nvim-ruby&#39;
</code></pre><p>to this:</p>
<pre tabindex="0"><code># bat ~/.tmux/resurrect/last --plain

pane    dotfiles    0   1   :*  0   nvim ~/dotfiles :/home/haseeb/dotfiles  1   nvim    :nvim
</code></pre><p>NOTE: we are just left with <code>:nvim</code> as the binary to run (from tmux-resurrect perspective).
Then to restore my neovim session we could use a plugin which creates a <code>Session.vim</code> file in the local directory
of that project, that tmux-resurrect can use to restore the actual neovim session rather than just running neovim.</p>
<p>However I don&rsquo;t want to manage session files like that. So I use <a href="https://github.com/rmagatti/auto-session"> <code>auto-session</code> </a>
which checks if a session exists in a specific directory and if it does it automatically restores the last saved session
when neovim opens.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://discourse.nixos.org/t/how-to-get-tmux-resurrect-to-restore-neovim-sessions/30819/2">Discourse Thread</a></li>
<li><a href="https://gitlab.com/hmajid2301/dotfiles/-/blob/06bf4ad267beb6693b941ef51d880e4d0fc1df0a/home-manager/programs/multiplexers/tmux.nix#L136-147">My tmux config</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Use Cachix Devenv to Setup Developer Environments</title>
      <link>https://haseebmajid.dev/posts/2023-08-26-how-to-use-cachix-devenv-to-setup-developer-environments/</link>
      <pubDate>Sat, 26 Aug 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-08-26-how-to-use-cachix-devenv-to-setup-developer-environments/</guid>
      <description>&lt;p&gt;In this post, I will go over how you can use Cachix&amp;rsquo;s &lt;a href=&#34;https://devenv.sh/&#34;&gt;devenv&lt;/a&gt; tool to help create/set up consistent
repeatable developer environments. You could use nix flakes if you wanted to as well, without needing another tool.
However, I like how devenv provides a few other &amp;ldquo;tools&amp;rdquo; within that we can set up from a single &lt;code&gt;devenv.nix&lt;/code&gt; file. Such as
pre-commit hooks, container support etc.&lt;/p&gt;
&lt;p&gt;This blog leverages devenv to create/set up its developer environment.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this post, I will go over how you can use Cachix&rsquo;s <a href="https://devenv.sh/">devenv</a> tool to help create/set up consistent
repeatable developer environments. You could use nix flakes if you wanted to as well, without needing another tool.
However, I like how devenv provides a few other &ldquo;tools&rdquo; within that we can set up from a single <code>devenv.nix</code> file. Such as
pre-commit hooks, container support etc.</p>
<p>This blog leverages devenv to create/set up its developer environment.</p>
<p><strong>Note:</strong> I&rsquo;m pretty new to using devenv myself so I&rsquo;m probably going to make a follow-up post as my developer workflow
changes.</p>
<h2 id="why-use-devenv">Why Use devenv</h2>
<p>Well, I mainly use it in my projects in two main ways.</p>
<p>To set up pre-commit hooks and to make sure certain binaries and tools are available. Imagine another developer
cloning our project doesn&rsquo;t need to make sure they have say <code>hugo</code> or <code>go-task</code> globally available.
It is set up automatically if they are using <code>devenv</code> and <a href="https://direnv.net/"><code>direnv</code></a>.</p>
<p>It can also be used to manage services like Postgresql.</p>
<h2 id="install-devenv">Install Devenv</h2>
<p>First I will assume you are using home-manager to manage your nix environment and are using nix flakes.
So first let&rsquo;s install devenv, go to <code>flake.nix</code> and add the following input:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl">  <span class="n">inputs</span> <span class="err">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">devenv</span><span class="o">.</span><span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;github:cachix/devenv/latest&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span></code></pre></div><p>This will make it available to the rest of our configuration as input. Now my flake config looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl">  <span class="n">outputs</span> <span class="err">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">self</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">nixpkgs</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">home-manager</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="o">@</span> <span class="n">inputs</span><span class="p">:</span> <span class="k">let</span>
</span></span><span class="line"><span class="cl">    <span class="k">inherit</span> <span class="p">(</span><span class="n">self</span><span class="p">)</span> <span class="n">outputs</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">lib</span> <span class="o">=</span> <span class="n">nixpkgs</span><span class="o">.</span><span class="n">lib</span> <span class="o">//</span> <span class="n">home-manager</span><span class="o">.</span><span class="n">lib</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">systems</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;x86_64-linux&#34;</span> <span class="s2">&#34;aarch64-linux&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="n">forEachSystem</span> <span class="o">=</span> <span class="n">f</span><span class="p">:</span> <span class="n">lib</span><span class="o">.</span><span class="n">genAttrs</span> <span class="n">systems</span> <span class="p">(</span><span class="n">sys</span><span class="p">:</span> <span class="n">f</span> <span class="n">pkgsFor</span><span class="o">.</span><span class="si">${</span><span class="n">sys</span><span class="si">}</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">pkgsFor</span> <span class="o">=</span> <span class="n">nixpkgs</span><span class="o">.</span><span class="n">legacyPackages</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">in</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">inherit</span> <span class="n">lib</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">homeConfigurations</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="c1"># Desktops</span>
</span></span><span class="line"><span class="cl">      <span class="n">mesmer</span> <span class="o">=</span> <span class="n">lib</span><span class="o">.</span><span class="n">homeManagerConfiguration</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">modules</span> <span class="o">=</span> <span class="p">[</span><span class="sr">./hosts/mesmer/home.nix</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="n">pkgs</span> <span class="o">=</span> <span class="n">nixpkgs</span><span class="o">.</span><span class="n">legacyPackages</span><span class="o">.</span><span class="n">x86_64-linux</span><span class="p">;</span>
</span></span><span class="line hl"><span class="cl">        <span class="n">extraSpecialArgs</span> <span class="o">=</span> <span class="p">{</span><span class="k">inherit</span> <span class="n">inputs</span> <span class="n">outputs</span><span class="p">;};</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span></code></pre></div><p>The home module inherits the inputs from the flake file, so we can access the devenv input in our home-manager
config. Then in in our <code>home.nix</code> module we can add something:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">inputs</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">pkgs</span><span class="o">,</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">home</span><span class="o">.</span><span class="n">packages</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="n">inputs</span><span class="o">.</span><span class="n">devenv</span><span class="o">.</span><span class="n">packages</span><span class="o">.</span><span class="s2">&#34;</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">system</span><span class="si">}</span><span class="s2">&#34;</span><span class="o">.</span><span class="n">devenv</span>
</span></span><span class="line"><span class="cl">    <span class="n">pkgs</span><span class="o">.</span><span class="n">cachix</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">programs</span><span class="o">.</span><span class="n">direnv</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">nix-direnv</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>In my config, I have this in its module called <code>devenv.nix</code>, because I like to split up my config.
To install devenv we can do <code>inputs.devenv.packages.&quot;${pkgs.system}&quot;.devenv</code>. We also need cachix (I think), so *
installed that from Nix packages. Like we would with any other package.</p>
<p>You will also notice I setup another tool called <code>direnv</code>, which is a generic tool that allows us to create a new shell
env when we change directories if there is a <code>.envrc</code>, where we can load things like env variables etc.</p>
<p>However, we can also leverage it to auto-run out devenv development environment i.e. <code>devenv shell</code> for us.
If set up devenv will create a <code>.envrc</code> file which contains <code>devenv use</code>. So when we change the directory, to one with devenv
setup it will set up our devenv environment automatically.</p>
<p><strong>Note</strong>: The first time we use direnv we need to run <code>direnv allow</code>, the output on your shell will remind you to do this.</p>
<p>After this, you can run your normal command to update your state using home-manager i.e.
<code>home-manager switch --flake ~/dotfiles#mesmer</code>.</p>
<p>Without <code>direnv</code> we could also run <code>devenv shell</code>, however, I found this would change my shell to bash whereas I normally
use fish.</p>
<h2 id="create-an-env">Create an env</h2>
<p>Okay now that we have devenv installed, let&rsquo;s set up our first devenv.
First we run <code>devenv init</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">devenv init
</span></span><span class="line"><span class="cl">Creating .envrc
</span></span><span class="line"><span class="cl">Creating devenv.nix
</span></span><span class="line"><span class="cl">Creating devenv.yaml
</span></span><span class="line"><span class="cl">Appending defaults to .gitignore
</span></span><span class="line"><span class="cl">Done.
</span></span><span class="line"><span class="cl">direnv is installed. Running direnv allow.
</span></span><span class="line"><span class="cl">direnv: loading ~/Downloads/.envrc
</span></span><span class="line"><span class="cl">direnv: loading https://raw.githubusercontent.com/cachix/devenv/d1f7b48e35e6dee421cfd0f51481d17f77586997/direnvrc <span class="o">(</span>sha256-YBzqskFZxmNb3kYVoKD9ZixoPXJh1C9ZvTLGFRkauZ0<span class="o">=)</span>
</span></span><span class="line"><span class="cl">direnv: using devenv
</span></span><span class="line"><span class="cl">direnv: .envrc changed, reloading
</span></span><span class="line"><span class="cl">Building shell ...
</span></span><span class="line"><span class="cl">warning: creating lock file <span class="s1">&#39;/home/haseeb/Downloads/devenv.lock&#39;</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span>1/4 built, 1/0/1 copied <span class="o">(</span>4.2/42.7 MiB<span class="o">)</span>, 4.1/40.5 MiB DL<span class="o">]</span> fetching git-2.41.0-debug from https://cache.nixos.orgdirenv: <span class="o">([</span>/nix/store/h77a0hqm3jcfqq7fgs310rf5l9w9g66y-direnv-2.32.3/bin/direnv <span class="nb">export</span> fish<span class="o">])</span> is taking a <span class="k">while</span> to execute. Use CTRL-C to give up.
</span></span><span class="line"><span class="cl">direnv: updated devenv shell cache
</span></span><span class="line"><span class="cl">hello from devenv
</span></span><span class="line"><span class="cl">git version 2.41.0
</span></span><span class="line"><span class="cl">direnv: <span class="nb">export</span> +C_INCLUDE_PATH +DEVENV_DOTFILE +DEVENV_PROFILE +DEVENV_ROOT +DEVENV_STATE +GREET +IN_NIX_SHELL +LIBRARY_PATH +PKG_CONFIG_PATH +name ~LD_LIBRARY_PATH ~PATH ~XDG_CONFIG_DIRS ~XDG_DATA_DIRS
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">ls -al
</span></span></code></pre></div><p>If we explore a bit more:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">~/Downloads
</span></span><span class="line"><span class="cl">❯ exa -al
</span></span><span class="line"><span class="cl">drwxr-xr-x    - haseeb <span class="m">25</span> Aug 20:00 .devenv
</span></span><span class="line"><span class="cl">.rw-r--r-- 3.4k haseeb <span class="m">25</span> Aug 19:59 .devenv.flake.nix
</span></span><span class="line"><span class="cl">drwxr-xr-x    - haseeb <span class="m">25</span> Aug 20:00 .direnv
</span></span><span class="line"><span class="cl">.rw-r--r--  <span class="m">176</span> haseeb <span class="m">25</span> Aug 19:59 .envrc
</span></span><span class="line"><span class="cl">.rw-r--r--   <span class="m">93</span> haseeb <span class="m">25</span> Aug 19:59 .gitignore
</span></span><span class="line"><span class="cl">.rw-r--r--  <span class="m">474</span> haseeb <span class="m">23</span> Aug 23:02 config.yml
</span></span><span class="line"><span class="cl">.rw-r--r-- 4.1k haseeb <span class="m">25</span> Aug 19:59 devenv.lock
</span></span><span class="line"><span class="cl">.rw-r--r--  <span class="m">567</span> haseeb <span class="m">25</span> Aug 19:59 devenv.nix
</span></span><span class="line"><span class="cl">.rw-r--r--   <span class="m">66</span> haseeb <span class="m">25</span> Aug 19:59 devenv.yaml
</span></span><span class="line"><span class="cl">direnv: error /home/haseeb/Downloads/a/.envrc is blocked. Run <span class="sb">`</span>direnv allow<span class="sb">`</span> to approve its content
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">~/Downloads
</span></span><span class="line"><span class="cl">❯ direnv allow
</span></span><span class="line"><span class="cl">direnv: loading ~/Downloads/a/.envrc
</span></span><span class="line"><span class="cl">direnv: loading https://raw.githubusercontent.com/cachix/devenv/d1f7b48e35e6dee421cfd0f51481d17f77586997/direnvrc <span class="o">(</span>sha256-YBzqskFZxmNb3kYVoKD9ZixoPXJh1C9ZvTLGFRkauZ0<span class="o">=)</span>
</span></span><span class="line"><span class="cl">direnv: using devenv
</span></span><span class="line"><span class="cl">direnv: .envrc changed, reloading
</span></span><span class="line"><span class="cl">Building shell ...
</span></span><span class="line"><span class="cl">direnv: updated devenv shell cache
</span></span><span class="line"><span class="cl">hello from devenv
</span></span><span class="line"><span class="cl">git version 2.41.0
</span></span><span class="line"><span class="cl">direnv: <span class="nb">export</span> +C_INCLUDE_PATH +DEVENV_DOTFILE +DEVENV_PROFILE +DEVENV_ROOT +DEVENV_STATE +GREET +IN_NIX_SHELL +LIBRARY_PATH +PKG_CONFIG_PATH +name ~LD_LIBRARY_PATH ~PATH ~XDG_CONFIG_DIRS ~XDG_DATA_DIRS
</span></span></code></pre></div><p>We&rsquo;ve now set up direnv so it will run our devenv env automatically.</p>
<h3 id="devenvnix">devenv.nix</h3>
<p>The meat and potatoes of our environment exist here, so let us open the file it will look like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span> <span class="n">pkgs</span><span class="o">,</span> <span class="o">...</span> <span class="p">}:</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># https://devenv.sh/basics/</span>
</span></span><span class="line"><span class="cl">  <span class="n">env</span><span class="o">.</span><span class="n">GREET</span> <span class="o">=</span> <span class="s2">&#34;devenv&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># https://devenv.sh/packages/</span>
</span></span><span class="line"><span class="cl">  <span class="n">packages</span> <span class="o">=</span> <span class="p">[</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">git</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># https://devenv.sh/scripts/</span>
</span></span><span class="line"><span class="cl">  <span class="n">scripts</span><span class="o">.</span><span class="n">hello</span><span class="o">.</span><span class="n">exec</span> <span class="o">=</span> <span class="s2">&#34;echo hello from $GREET&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">enterShell</span> <span class="o">=</span> <span class="s1">&#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">    hello
</span></span></span><span class="line"><span class="cl"><span class="s1">    git --version
</span></span></span><span class="line"><span class="cl"><span class="s1">  &#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># https://devenv.sh/languages/</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># languages.nix.enable = true;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># https://devenv.sh/pre-commit-hooks/</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># pre-commit.hooks.shellcheck.enable = true;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># https://devenv.sh/processes/</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># processes.ping.exec = &#34;ping example.com&#34;;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># See full reference at https://devenv.sh/reference/options/</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h4 id="entershell">enterShell</h4>
<p>This also explains some of the input we saw above i.e</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">hello from devenv
</span></span><span class="line"><span class="cl">git version 2.41.0
</span></span></code></pre></div><p>Which matches what&rsquo;s in our <code>enterShell</code>, so this is run when we enter the devenv environment.</p>
<h4 id="env">env</h4>
<p>We can also set ENV variables using the <code>env</code> i.e. <code>env.GREET</code> makes the greet env variable inside the devenv.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$GREET</span>
</span></span><span class="line"><span class="cl">devenv
</span></span></code></pre></div><h3 id="packages">packages</h3>
<p>These are packages we want to be available in our devenv, that we don&rsquo;t need to globally installed. These are the same
ones available on <a href="https://search.nixos.org/packages?channel=unstable&amp;from=0&amp;size=50&amp;sort=relevance&amp;type=packages&amp;query=git">nixos pkgs</a>.</p>
<p>We can search for packages on the cli using <code>devenv search</code> i.e.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">devenv search go_1_19
</span></span><span class="line"><span class="cl">name          version  description
</span></span><span class="line"><span class="cl">----          -------  -----------
</span></span><span class="line"><span class="cl">pkgs.go_1_19  1.19.12  The Go Programming language
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">No options found <span class="k">for</span> <span class="s1">&#39;go_1_19&#39;</span>.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Found <span class="m">1</span> packages and <span class="m">0</span> options <span class="k">for</span> <span class="s1">&#39;go_1_19&#39;</span>.
</span></span></code></pre></div><p>Now this will guarantee that these packages are available within our devenv. This for me is one of the biggest reasons
to use devenv. So now other devs don&rsquo;t need to make sure they have certain tools installed globally. Such as say
<code>jq</code>, we can just make them available in a devenv.</p>
<h4 id="scripts">scripts</h4>
<p>We can also make shell scripts available in a single location like the <code>hello</code> script above. This works well for simple
one-liners. Then within the devenv we can do:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"> hello
</span></span><span class="line"><span class="cl">hello from devenv
</span></span></code></pre></div><p>We can also specify tools to have available for shell script but not make them available in the devenv. We can do
something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="n">scripts</span><span class="o">.</span><span class="n">silly-example</span><span class="o">.</span><span class="n">exec</span> <span class="err">=</span> <span class="s1">&#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">    </span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">curl</span><span class="si">}</span><span class="s1">/bin/curl &#34;https://httpbin.org/get?$1&#34; | </span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">jq</span><span class="si">}</span><span class="s1">/bin/jq &#39;.args&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">  &#39;&#39;</span><span class="p">;</span>
</span></span></code></pre></div><p>This means the script can use <code>jq</code> and <code>curl</code>.</p>
<h4 id="pre-commit">pre-commit</h4>
<p>The other main construct I use is pre-commit hooks, this will auto-generate a <code>.pre-commit-config.yaml</code> and add it
to our <code>.gitignore</code>. As it is generated from the <code>devenv.nix</code> file. We can define them like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl">  <span class="n">pre-commit</span><span class="o">.</span><span class="n">hooks</span> <span class="err">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># built in</span>
</span></span><span class="line"><span class="cl">    <span class="n">shellcheck</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># custom</span>
</span></span><span class="line"><span class="cl">    <span class="n">golangci-lint</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">name</span> <span class="o">=</span> <span class="s2">&#34;golangci-lint&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">description</span> <span class="o">=</span> <span class="s2">&#34;Lint my golang code&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">files</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="se">\.</span><span class="s2">go$&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">entry</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">golangci-lint</span><span class="si">}</span><span class="s2">/bin/golangci-lint run --new-from-rev HEAD --fix&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">require_serial</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">pass_filenames</span> <span class="o">=</span> <span class="no">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span></code></pre></div><p>The <code>spellcheck</code> is a <a href="https://devenv.sh/reference/options/#pre-commithooks">builtin hooks</a>.
Where <code>golangci-lint</code>, is a custom hook we have defined ourselves.</p>
<h4 id="updating">Updating</h4>
<p>Finally, if we want to update the nixpkgs for example, say when a new version of Golang releases. Normally we could
<code>nix flake update</code>, to update all of our nix flake inputs. We can do the same devenv we do <code>devenv update</code>.
This updates the <code>devenv.lock</code>, which is the same as <code>flake.lock</code> file.</p>
<p>The lock files tie us to a specific version of nixpkgs so that means as long as this file stays the same we will install the version of all the tools as anyone else who runs <code>devenv</code>.</p>
<h3 id="why-not-devcontainers">Why not devcontainers?</h3>
<p>So as a slight aside you might be asking why not use <a href="https://containers.dev/">devcontainers</a>. Well, the main reason
for not doing devcontainers is that I lose access to my shell, with all of my tools. I needed to do some funky stuff
with a <a href="https://haseebmajid.dev/posts/2022-12-15-how-to-use-dotbot-to-personalise-your-vscode-devcontainers/">dotfiles repo</a>
and a dotfiles script. It ended up slowing me down more than providing value.</p>
<p>I think Nix Flakes/devenv provides a good middle ground. They are also naturally far more reproducible than docker containers
or dev containers.</p>
<p><strong>P.S:</strong> At some point I will try out <a href="https://www.jetpack.io/devbox/docs/">devbox</a> and normal
<a href="https://github.com/the-nix-way/dev-templates"> nix flakes </a>.</p>
<p>That&rsquo;s It! We set up a devenv!</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/gomodoro-cli">Go Project using devenv</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Colour Neovim Line Numbers</title>
      <link>https://haseebmajid.dev/posts/2023-08-24-til-how-to-colour-neovim-line-numbers/</link>
      <pubDate>Thu, 24 Aug 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-08-24-til-how-to-colour-neovim-line-numbers/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Colour Neovim Line Numbers&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Recently I was trying to configure my own neovim config. I wanted the current line I was onto be coloured slighty
differently and also the line number to be white. So it&amp;rsquo;s easier to see the line number and which line I was on.&lt;/p&gt;
&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2023-08-24-til-how-to-colour-neovim-line-numbers/images/highlight.png&#34;
        type=&#34;&#34;
        alt=&#34;Highlight&#34;
        
      /&gt;&lt;/p&gt;
&lt;p&gt;It was not immediately obvious how to do this and took me long to work out than I&amp;rsquo;d like to admit 😅.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Colour Neovim Line Numbers</strong></p>
<p>Recently I was trying to configure my own neovim config. I wanted the current line I was onto be coloured slighty
differently and also the line number to be white. So it&rsquo;s easier to see the line number and which line I was on.</p>
<p><img
        loading="lazy"
        src="/posts/2023-08-24-til-how-to-colour-neovim-line-numbers/images/highlight.png"
        type=""
        alt="Highlight"
        
      /></p>
<p>It was not immediately obvious how to do this and took me long to work out than I&rsquo;d like to admit 😅.</p>
<p>First we need to create to two highlight groups:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="c1">-- Highlights the line</span>
</span></span><span class="line"><span class="cl"><span class="n">CursorLine</span> <span class="o">=</span> <span class="p">{</span> <span class="n">fg</span> <span class="o">=</span> <span class="s1">&#39;NONE&#39;</span><span class="p">,</span> <span class="n">bg</span> <span class="o">=</span> <span class="n">c.black2</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">-- Highlights the line number</span>
</span></span><span class="line"><span class="cl"><span class="n">CursorLineNr</span> <span class="o">=</span> <span class="p">{</span> <span class="n">fg</span> <span class="o">=</span> <span class="n">c.white</span> <span class="p">},</span>
</span></span></code></pre></div><p>Then in our vim options set the following <code>vim.o.cursorline = true</code> (this is what I was missing, in my case).</p>
<p>Anyways that&rsquo;s it! A really quick post, on something stupid I got stuck on!</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>How To Self Host Your Own Atuin Server</title>
      <link>https://haseebmajid.dev/posts/2023-08-16-how-self-host-your-own-atuin-server/</link>
      <pubDate>Wed, 16 Aug 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-08-16-how-self-host-your-own-atuin-server/</guid>
      <description>&lt;p&gt;In this article, we will go over how we can &lt;a href=&#34;https://atuin.sh/docs/self-hosting/&#34;&gt;self-host&lt;/a&gt; our instance of &lt;a href=&#34;https://atuin.sh/&#34;&gt;Atuin&lt;/a&gt;.
A tool we can use to sync our shell history across multiple devices. In the previous article, I showed how you can
use the official server. However you may want to run your self-hosted one, so no one can access even the
encrypted version of your shell history.&lt;/p&gt;
&lt;p&gt;We will deploy our instance to fly.io. Why fly.io, its pretty easy to deploy they have a great CLI tool. Also
we can get a Postgres database deployed, which we need with &lt;code&gt;Atuin&lt;/code&gt;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this article, we will go over how we can <a href="https://atuin.sh/docs/self-hosting/">self-host</a> our instance of <a href="https://atuin.sh/">Atuin</a>.
A tool we can use to sync our shell history across multiple devices. In the previous article, I showed how you can
use the official server. However you may want to run your self-hosted one, so no one can access even the
encrypted version of your shell history.</p>
<p>We will deploy our instance to fly.io. Why fly.io, its pretty easy to deploy they have a great CLI tool. Also
we can get a Postgres database deployed, which we need with <code>Atuin</code>.</p>
<h2 id="fly-cli">Fly CLI</h2>
<p>Assuming you have the <a href="https://fly.io/docs/hands-on/install-flyctl/">flyctl cli tool</a> installed.
Assuming we have already authenticated using the cli tool <code>fly auth login</code> or <code>fly auth signup</code>.</p>
<p>We can create a new fly app by running <code>fly launch --image ghcr.io/ellie/atuin:main</code>. It will then
ask us some questions about where to deploy (region), under which organisation. Fill those out however you want.
When it asks you if you want a Postgresql database answer yes.</p>
<pre tabindex="0"><code>? Would you like to set up a Postgresql database now? Yes
</code></pre><p>This process will also create a <code>fly.toml</code> file in your current directory. We should add some environment
variables:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">env</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="nx">ATUIN_HOST</span><span class="p">=</span><span class="s2">&#34;0.0.0.0&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">ATUIN_PORT</span><span class="p">=</span><span class="mi">8888</span>
</span></span><span class="line"><span class="cl">  <span class="nx">ATUIN_OPEN_REGISTRATION</span><span class="p">=</span><span class="kc">true</span>
</span></span></code></pre></div><p>Then make sure in the HTTP service section the internal port is also <code>8888</code> like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">http_service</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="nx">internal_port</span> <span class="p">=</span> <span class="mi">8888</span>
</span></span></code></pre></div><h3 id="fetch-secrets">Fetch Secrets</h3>
<p>We also need to set another variable which we will set as a secret which is the database uri <code>ATUIN_DB_URI</code>.
As this will contain the username and the password to connect to the db i.e. <code>ATUIN_DB_URI=&quot;postgres://user:password@hostname/database&quot;</code>.</p>
<p>When Fly created the PostgreSQL it also added a <code>DATABASE_URL</code> env variable, which is what we need the value of to use with the <code>ATUIN_DB_URI</code>.
We can get this variable by &ldquo;execing&rdquo; into the container and dumping the env variables out. However, we need to make sure to keep the container
alive long enough for us to exec in. By default, the app will try to connect to Postgresql fail and fly.io will say it failed to deploy.</p>
<p>Add this to our toml file, which is equivalent to the cmd and entrypoint in our docker or docker-compose files.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">experimental</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">cmd</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;tail&#34;</span><span class="p">,</span> <span class="s2">&#34;-f&#34;</span><span class="p">,</span> <span class="s2">&#34;/dev/null&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">entrypoint</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;/bin/bash&#34;</span><span class="p">]</span>
</span></span></code></pre></div><p>Then we can redeploy by running <code>fly deploy</code>, to deploy the new version of the app.
Then lets ssh into our app:</p>
<p><code>fly ssh console</code></p>
<p>and dump the environment variables <code>env</code> and copy the value of the <code>DATABASE_URL</code>.</p>
<h3 id="set-db-secrets">Set DB Secrets</h3>
<p>Then we can set the secret:</p>
<p><code>fly secrets set ATUIN_DB_URI=postgres://example.com/mydb --stage</code></p>
<p>and remove the experimental bit from our toml file so it looks something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="c"># fly.toml app configuration file generated for majiy00-shell on 2023-07-29T14:31:15+01:00</span>
</span></span><span class="line"><span class="cl"><span class="c">#</span>
</span></span><span class="line"><span class="cl"><span class="c"># See https://fly.io/docs/reference/configuration/ for information about how to use this file.</span>
</span></span><span class="line"><span class="cl"><span class="c">#</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">app</span> <span class="p">=</span> <span class="s2">&#34;majiy00-shell&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">primary_region</span> <span class="p">=</span> <span class="s2">&#34;lhr&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">build</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="nx">image</span> <span class="p">=</span> <span class="s2">&#34;ghcr.io/ellie/atuin:main&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">env</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="nx">ATUIN_HOST</span><span class="p">=</span><span class="s2">&#34;0.0.0.0&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">ATUIN_PORT</span><span class="p">=</span><span class="mi">8888</span>
</span></span><span class="line"><span class="cl">  <span class="nx">ATUIN_OPEN_REGISTRATION</span><span class="p">=</span><span class="kc">true</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">http_service</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="nx">internal_port</span> <span class="p">=</span> <span class="mi">8888</span>
</span></span><span class="line"><span class="cl">  <span class="nx">force_https</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">  <span class="nx">auto_stop_machines</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">  <span class="nx">auto_start_machines</span> <span class="p">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">  <span class="nx">min_machines_running</span> <span class="p">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">  <span class="nx">processes</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;app&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">experimental</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="nx">cmd</span> <span class="p">=</span> <span class="p">[</span><span class="s2">&#34;server&#34;</span><span class="p">,</span> <span class="s2">&#34;start&#34;</span><span class="p">]</span>
</span></span></code></pre></div><p>Then run <code>fly deploy</code> and update your config to point to this version i.e. majiy00-shell.fly.dev (sync_adress config option).</p>
<blockquote>
<p>P.S. Don&rsquo;t forget to set the <code>ATUIN_OPEN_REGISTRATION</code> to false if you don&rsquo;t want anyone else to be able to create accounts on your instance.</p>
</blockquote>
]]></content:encoded>
    </item>
    
    <item>
      <title>How Sync Your Shell History With Atuin in Nix</title>
      <link>https://haseebmajid.dev/posts/2023-08-12-how-sync-your-shell-history-with-atuin-in-nix/</link>
      <pubDate>Sat, 12 Aug 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-08-12-how-sync-your-shell-history-with-atuin-in-nix/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://atuin.sh/docs/&#34;&gt;Atuin&lt;/a&gt; is a great tool I recently discovered that can be used to sync our shell history across
multiple machines. We can either self-host this or use the &amp;ldquo;officially&amp;rdquo; hosted one. In a future article, I will show
you how you can self-host your version of Atuin on fly.io. But for this article, I will assume you have a server
setup. Your history is end-to-end encrypted so the official server is safe to use and store your history on.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><a href="https://atuin.sh/docs/">Atuin</a> is a great tool I recently discovered that can be used to sync our shell history across
multiple machines. We can either self-host this or use the &ldquo;officially&rdquo; hosted one. In a future article, I will show
you how you can self-host your version of Atuin on fly.io. But for this article, I will assume you have a server
setup. Your history is end-to-end encrypted so the official server is safe to use and store your history on.</p>
<p>It supports the main shells:</p>
<ul>
<li>fish</li>
<li>bash</li>
<li>zsh</li>
<li>nushell</li>
</ul>
<h2 id="setup">Setup</h2>
<p>Now let&rsquo;s see how we can setup this up on our Nix machine, in this example I will be using home-manager. There is a
<a href="https://mipmip.github.io/home-manager-option-search/?query=atuin">home-manager module</a> which makes setting up Atuin a lot easier.</p>
<p>We can create a new file called <code>atuin.nix</code> which looks a bit like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span> <span class="n">config</span><span class="o">,</span> <span class="o">...</span> <span class="p">}:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">programs</span><span class="o">.</span><span class="n">atuin</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">settings</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="c1"># Uncomment this to use your instance</span>
</span></span><span class="line"><span class="cl">      <span class="c1"># sync_address = &#34;https://majiy00-shell.fly.dev&#34;;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Where the settings are the various config options that Atuin lets us set.
You can find more of <a href="https://atuin.sh/docs/config/">them here</a>.</p>
<h3 id="first-machine">First Machine</h3>
<p>On our first machine, before we synced our shell history, we need to create an account. To do run the following:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">atuin register -u &lt;YOUR_USERNAME&gt; -e &lt;YOUR EMAIL&gt;
</span></span></code></pre></div><p>Then we want to get our key, <code>atuin key</code>. This is our encryption key and should be kept PRIVATE 🔒.
Don&rsquo;t share it with anyone, store it somewhere safe. If you lose this key you won&rsquo;t be able to recover your shell
history from Atuin anymore.</p>
<p>Next, we can import our existing shell history to Atuin by running <code>atuin import auto</code>, which will import history
from our current shell. We can also specify which shell to use <code>atuin import zsh</code> for example.</p>
<h4 id="sops-nix">sops-nix</h4>
<p>If you are using a tool like <a href="https://github.com/Mic92/sops-nix">sops-nix</a> to manage secrets in nix. We can add
the following lines to our code, which contain the Atuin key. So we don&rsquo;t have to manually copy the key between
multiple devices.</p>
<p>Again I&rsquo;ll do another article on how we can set up sops-nix with home-manager at some point.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span> <span class="n">config</span><span class="o">,</span> <span class="o">...</span> <span class="p">}:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">programs</span><span class="o">.</span><span class="n">atuin</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">settings</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">key_path</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">sops</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">atuin_key</span><span class="o">.</span><span class="n">path</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">sops</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">atuin_key</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">sopsFile</span> <span class="o">=</span> <span class="sr">../secrets.yaml</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="other-machines">Other Machines</h3>
<p>You will need to have your Atuin key available, either manually copied over or by using a secret manager as I
used with sops-nix. Don&rsquo;t store your Atuin key directly in source control.</p>
<p>Then we can do something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">atuin login -u &lt;USERNAME&gt;
</span></span><span class="line"><span class="cl">atuin sync
</span></span></code></pre></div><h3 id="gotcha">Gotcha</h3>
<p>If you notice an error like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">atuin account register
</span></span><span class="line"><span class="cl">Please enter username: hmajid2301
</span></span><span class="line"><span class="cl">Please enter email: hello@haseebmajid.dev
</span></span><span class="line"><span class="cl">Please enter password:
</span></span><span class="line"><span class="cl">Error: error decoding response body: expected value at line <span class="m">1</span> column <span class="m">1</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Caused by:
</span></span><span class="line"><span class="cl">    expected value at line <span class="m">1</span> column <span class="m">1</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Location:
</span></span><span class="line"><span class="cl">    /build/source/atuin-client/src/api_client.rs:63:21
</span></span></code></pre></div><p>Make sure your sync address does not end in <code>/</code> i.e. <code>atuin.dev</code> instead
of <code>atuin.dev/</code></p>
<h3 id="my-config">My config</h3>
<p>Here is my Atuin config, where I am using a self-hosted server. I also set a sync frequency of 15 minutes.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span> <span class="n">config</span><span class="o">,</span> <span class="o">...</span> <span class="p">}:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">programs</span><span class="o">.</span><span class="n">atuin</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">settings</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">sync_address</span> <span class="o">=</span> <span class="s2">&#34;https://majiy00-shell.fly.dev&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">sync_frequency</span> <span class="o">=</span> <span class="s2">&#34;15m&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">dialect</span> <span class="o">=</span> <span class="s2">&#34;uk&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">key_path</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">sops</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">atuin_key</span><span class="o">.</span><span class="n">path</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">sops</span><span class="o">.</span><span class="n">secrets</span><span class="o">.</span><span class="n">atuin_key</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">sopsFile</span> <span class="o">=</span> <span class="sr">../secrets.yaml</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>That&rsquo;s It! We now have Atuin syncing our shell history between multiple.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://atuin.sh/docs/config/">Atuin</a></li>
<li><a href="https://gitlab.com/hmajid2301/dotfiles/-/blob/0c12ac20c3ab08fa3e76352c4e352a4adb9c3c9a/home-manager/atuin/default.nix">My nix config</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Setup Neovim for Nixlang</title>
      <link>https://haseebmajid.dev/posts/2023-08-06-til-how-to-setup-neovim-for-nixlang/</link>
      <pubDate>Sun, 06 Aug 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-08-06-til-how-to-setup-neovim-for-nixlang/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Setup Neovim for Nixlang&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I have been recently using NixOS/home-manager and I have been writing a lot of nixlang. To have my system state
declaratively set up. I have been doing most of this editing in neovim. It took me a bit of time to work out how to get
it set up so there is some basic LSP support and auto-formatting. I created a file called &lt;code&gt;nix.lua&lt;/code&gt; and it looks like this:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Setup Neovim for Nixlang</strong></p>
<p>I have been recently using NixOS/home-manager and I have been writing a lot of nixlang. To have my system state
declaratively set up. I have been doing most of this editing in neovim. It took me a bit of time to work out how to get
it set up so there is some basic LSP support and auto-formatting. I created a file called <code>nix.lua</code> and it looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="kr">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1">-- Correctly setup lspconfig for Nix 🚀</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;neovim/nvim-lspconfig&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">opts</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">servers</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="c1">-- Ensure mason installs the server</span>
</span></span><span class="line"><span class="cl">          <span class="n">rnix</span> <span class="o">=</span> <span class="p">{},</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="n">settings</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">rnix</span> <span class="o">=</span> <span class="p">{},</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;jose-elias-alvarez/null-ls.nvim&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">opts</span> <span class="o">=</span> <span class="kr">function</span><span class="p">(</span><span class="n">_</span><span class="p">,</span> <span class="n">opts</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="kd">local</span> <span class="n">nls</span> <span class="o">=</span> <span class="n">require</span><span class="p">(</span><span class="s2">&#34;null-ls&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="kr">if</span> <span class="n">type</span><span class="p">(</span><span class="n">opts.sources</span><span class="p">)</span> <span class="o">==</span> <span class="s2">&#34;table&#34;</span> <span class="kr">then</span>
</span></span><span class="line"><span class="cl">          <span class="n">vim.list_extend</span><span class="p">(</span><span class="n">opts.sources</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">              <span class="n">nls.builtins</span><span class="p">.</span><span class="n">code_actions.statix</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="n">nls.builtins</span><span class="p">.</span><span class="n">formatting.alejandra</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="n">nls.builtins</span><span class="p">.</span><span class="n">diagnostics.deadnix</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="p">})</span>
</span></span><span class="line"><span class="cl">      <span class="kr">end</span>
</span></span><span class="line"><span class="cl">    <span class="kr">end</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This config will make sure we have some basic LSP i.e. missing <code>pkgs</code> definition in our expressions. It will
auto-format our code as well. Which is very helpful.</p>
<blockquote>
<p>P.S. I still don&rsquo;t have any form of auto-complete setup. If you have any ideas I&rsquo;m all ears.</p>
</blockquote>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/dotfiles/-/blob/618ea7283587c19b76e860a7a7fd20c0c1ba53e2/home-manager/editors/nvim/config/lua/plugins/nix.lua">My nix.lua</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Fix VIA Permission Issues on Linux</title>
      <link>https://haseebmajid.dev/posts/2023-08-02-til-how-to-fix-via-permission-issues-on-linux/</link>
      <pubDate>Wed, 02 Aug 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-08-02-til-how-to-fix-via-permission-issues-on-linux/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Fix VIA Permission Issues on Linux&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Recently I have been trying to configure my newish Keychron keyboard using &lt;a href=&#34;https://www.caniusevia.com/&#34;&gt;VIA&lt;/a&gt;.
To add a key to be able to take print screens, making use of the layers provided,
as my Keychron Q1 PRO doesn&amp;rsquo;t have a dedicated print key &amp;#x1f613;.&lt;/p&gt;
&lt;p&gt;However, when I opened the app or the &lt;a href=&#34;https://usevia.app/&#34;&gt;website&lt;/a&gt;, I would get
the following error.&lt;/p&gt;
&lt;p&gt;If we go to &lt;code&gt;chrome://device-log&lt;/code&gt; you should be able to see something like this:
&lt;code&gt;Failed to open &#39;/dev/hidraw9&#39;: FILE_ERROR_ACCESS_DENIED&lt;/code&gt;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Fix VIA Permission Issues on Linux</strong></p>
<p>Recently I have been trying to configure my newish Keychron keyboard using <a href="https://www.caniusevia.com/">VIA</a>.
To add a key to be able to take print screens, making use of the layers provided,
as my Keychron Q1 PRO doesn&rsquo;t have a dedicated print key &#x1f613;.</p>
<p>However, when I opened the app or the <a href="https://usevia.app/">website</a>, I would get
the following error.</p>
<p>If we go to <code>chrome://device-log</code> you should be able to see something like this:
<code>Failed to open '/dev/hidraw9': FILE_ERROR_ACCESS_DENIED</code>.</p>
<p><img
        loading="lazy"
        src="/posts/2023-08-02-til-how-to-fix-via-permission-issues-on-linux/images/error.png"
        type=""
        alt="Error"
        
      /></p>
<blockquote>
<p>hidraw enables raw access to USB and Bluetooth Human Interface (hidraw) devices.</p>
</blockquote>
<p>This means Chrome cannot access our keyboard, so to fix this we can do something like:</p>
<pre tabindex="0"><code>sudo chmod a+rw /dev/hidraw9
</code></pre><p>Then open the app/go to the website and you should see the errors are gone.</p>
<p><img
        loading="lazy"
        src="/posts/2023-08-02-til-how-to-fix-via-permission-issues-on-linux/images/loaded.png"
        type=""
        alt="Loaded"
        
      /></p>
<p>To set the original permissions you can do:</p>
<pre tabindex="0"><code>sudo chmod 600 /dev/hidraw8
</code></pre><h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://bbs.archlinux.org/viewtopic.php?id=285709">Arch Forum Post which helped me</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Use Neotest Dap With Lazyvim for Golang Development</title>
      <link>https://haseebmajid.dev/posts/2023-07-31-how-to-use-neotest-dap-with-lazyvim-for-golang-development/</link>
      <pubDate>Mon, 31 Jul 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-07-31-how-to-use-neotest-dap-with-lazyvim-for-golang-development/</guid>
      <description>&lt;p&gt;In this blog post, I will show you how you can easily setup Neotest to make testing easier in neovim and DAP to make
debugging your tests is a lot easier.&lt;/p&gt;
&lt;p&gt;I am using the &lt;a href=&#34;https://www.lazyvim.org/&#34;&gt;LazyVim&lt;/a&gt; neovim distribution, where these two come as easily installed extra
plugins. However, it should be easy enough for you to copy the config over to your neovim lua config.
LazyVim even has the config available on its website, you don&amp;rsquo;t even need to deep dive into the code &amp;#x1f604;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this blog post, I will show you how you can easily setup Neotest to make testing easier in neovim and DAP to make
debugging your tests is a lot easier.</p>
<p>I am using the <a href="https://www.lazyvim.org/">LazyVim</a> neovim distribution, where these two come as easily installed extra
plugins. However, it should be easy enough for you to copy the config over to your neovim lua config.
LazyVim even has the config available on its website, you don&rsquo;t even need to deep dive into the code &#x1f604;.</p>
<p>For example with <a href="https://www.lazyvim.org/extras/dap/core">dap</a>. Here are some examples of what it can look like. The two
main bits we will look at today don&rsquo;t come by default with LazyVim but are &ldquo;extras&rdquo; we can add them</p>
<h2 id="neotest">Neotest</h2>
<p>To set up Neotest on LazyVim, go to your LazyVim <a href="https://github.com/LazyVim/starter">starter repo</a>. Then open our
<code>lazy.lua</code> file and add the following lines:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="n">spec</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">-- add LazyVim and import its plugins</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="s2">&#34;LazyVim/LazyVim&#34;</span><span class="p">,</span> <span class="n">import</span> <span class="o">=</span> <span class="s2">&#34;lazyvim.plugins&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="c1">-- import any extras modules here</span>
</span></span><span class="line hl"><span class="cl">    <span class="p">{</span> <span class="n">import</span> <span class="o">=</span> <span class="s2">&#34;lazyvim.plugins.extras.test.core&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="n">import</span> <span class="o">=</span> <span class="s2">&#34;lazyvim.plugins.extras.lang.go&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="c1">-- import/override with your plugins</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="n">import</span> <span class="o">=</span> <span class="s2">&#34;plugins&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">},</span>
</span></span></code></pre></div><p>Here you can see the output from a test at the bottom (from Neotest) and a test summary (on the right).
Using the test summary we can easily run one test at a time, even subtests.</p>
<p><img
        loading="lazy"
        src="/posts/2023-07-31-how-to-use-neotest-dap-with-lazyvim-for-golang-development/images/test_summary.png"
        type=""
        alt="Test Summary"
        
      /></p>
<p>The config that LazyVim sets the following key binds.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="n">keys</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="s2">&#34;&lt;leader&gt;tt&#34;</span><span class="p">,</span> <span class="kr">function</span><span class="p">()</span> <span class="n">require</span><span class="p">(</span><span class="s2">&#34;neotest&#34;</span><span class="p">).</span><span class="n">run.run</span><span class="p">(</span><span class="n">vim.fn</span><span class="p">.</span><span class="n">expand</span><span class="p">(</span><span class="s2">&#34;%&#34;</span><span class="p">))</span> <span class="kr">end</span><span class="p">,</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Run File&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="s2">&#34;&lt;leader&gt;tT&#34;</span><span class="p">,</span> <span class="kr">function</span><span class="p">()</span> <span class="n">require</span><span class="p">(</span><span class="s2">&#34;neotest&#34;</span><span class="p">).</span><span class="n">run.run</span><span class="p">(</span><span class="n">vim.loop</span><span class="p">.</span><span class="n">cwd</span><span class="p">())</span> <span class="kr">end</span><span class="p">,</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Run All Test Files&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="s2">&#34;&lt;leader&gt;tr&#34;</span><span class="p">,</span> <span class="kr">function</span><span class="p">()</span> <span class="n">require</span><span class="p">(</span><span class="s2">&#34;neotest&#34;</span><span class="p">).</span><span class="n">run.run</span><span class="p">()</span> <span class="kr">end</span><span class="p">,</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Run Nearest&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="s2">&#34;&lt;leader&gt;ts&#34;</span><span class="p">,</span> <span class="kr">function</span><span class="p">()</span> <span class="n">require</span><span class="p">(</span><span class="s2">&#34;neotest&#34;</span><span class="p">).</span><span class="n">summary.toggle</span><span class="p">()</span> <span class="kr">end</span><span class="p">,</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Toggle Summary&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="s2">&#34;&lt;leader&gt;to&#34;</span><span class="p">,</span> <span class="kr">function</span><span class="p">()</span> <span class="n">require</span><span class="p">(</span><span class="s2">&#34;neotest&#34;</span><span class="p">).</span><span class="n">output.open</span><span class="p">({</span> <span class="n">enter</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span> <span class="n">auto_close</span> <span class="o">=</span> <span class="kc">true</span> <span class="p">})</span> <span class="kr">end</span><span class="p">,</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Show Output&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="s2">&#34;&lt;leader&gt;tO&#34;</span><span class="p">,</span> <span class="kr">function</span><span class="p">()</span> <span class="n">require</span><span class="p">(</span><span class="s2">&#34;neotest&#34;</span><span class="p">).</span><span class="n">output_panel.toggle</span><span class="p">()</span> <span class="kr">end</span><span class="p">,</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Toggle Output Panel&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="s2">&#34;&lt;leader&gt;tS&#34;</span><span class="p">,</span> <span class="kr">function</span><span class="p">()</span> <span class="n">require</span><span class="p">(</span><span class="s2">&#34;neotest&#34;</span><span class="p">).</span><span class="n">run.stop</span><span class="p">()</span> <span class="kr">end</span><span class="p">,</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Stop&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">},</span>
</span></span></code></pre></div><p>So we can get the test summary by pressing <code>&lt;space&gt; + t + s</code>.
Or test output using <code>&lt;space&gt; + t + shift + o</code>. Where <code>&lt;space&gt;</code> is my leader key. Also if a test fails when I run
the nearest test <code>&lt;space&gt; + t + n</code>, the output panel (at the bottom) auto opens.</p>
<h2 id="dap-core">DAP Core</h2>
<p>Similar to above to add debugging support we can add an extra plugin like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="n">spec</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">-- add LazyVim and import its plugins</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="s2">&#34;LazyVim/LazyVim&#34;</span><span class="p">,</span> <span class="n">import</span> <span class="o">=</span> <span class="s2">&#34;lazyvim.plugins&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="c1">-- import any extras modules here</span>
</span></span><span class="line hl"><span class="cl">    <span class="p">{</span> <span class="n">import</span> <span class="o">=</span> <span class="s2">&#34;lazyvim.plugins.extras.dap.core&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="c1">-- import/override with your plugins</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="n">import</span> <span class="o">=</span> <span class="s2">&#34;plugins&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">},</span>
</span></span></code></pre></div><p><img
        loading="lazy"
        src="/posts/2023-07-31-how-to-use-neotest-dap-with-lazyvim-for-golang-development/images/test_debug.png"
        type=""
        alt="Test Debug"
        
      /></p>
<p>To debug our tests first we can add a breakpoint using <code>&lt;space&gt; + d + b</code>. Then we can run our test in debug mode using
<code>&lt;space&gt; + t + d</code>, which will run the nearest test in <a href="https://github.com/LazyVim/LazyVim/blob/566049aa4a26a86219dd1ad1624f9a1bf18831b6/lua/lazyvim/plugins/extras/lang/go.lua#L20">debug mode</a>.</p>
<p>When we start to debug our tests it looks something like this. If you have used the VSCode debugger this will look very
similar. We can explore the current state, the values various variables have etc. Run commands in the debugger at the
bottom.</p>
<p>That&rsquo;s it! We can improve our testing experience in Go using neovim with Neotest and DAP. I am also able to replicate
some features I used to have in VSCode and now also have in neovim 💗.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://www.youtube.com/watch?v=7Nt8n3rjfDY">This video helping me get set up by Elijah Manor (he does great videos btw)</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to get Kanshi to work on NixOS and Hyprland</title>
      <link>https://haseebmajid.dev/posts/2023-07-25-nixos-kanshi-and-hyprland/</link>
      <pubDate>Tue, 25 Jul 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-07-25-nixos-kanshi-and-hyprland/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to get Kanshi to work on NixOS and Hyprland&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I have been using Kanshi to setup my monitor setups automagically depending on which monitors are plugged
i.e. if my laptop is docked or not. If it is docked I want my laptop display to be off, when not docked I want
it to be on. So my kanshi config file &lt;code&gt;~/.config/kanshi/config&lt;/code&gt; to look something like:&lt;/p&gt;
&lt;p&gt;I use the name of my monitors as the ports they are plugged into my vary.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to get Kanshi to work on NixOS and Hyprland</strong></p>
<p>I have been using Kanshi to setup my monitor setups automagically depending on which monitors are plugged
i.e. if my laptop is docked or not. If it is docked I want my laptop display to be off, when not docked I want
it to be on. So my kanshi config file <code>~/.config/kanshi/config</code> to look something like:</p>
<p>I use the name of my monitors as the ports they are plugged into my vary.</p>
<pre tabindex="0"><code>profile home_office {
  output &#34;GIGA-BYTE TECHNOLOGY CO., LTD. Gigabyte M32U 21351B000087&#34; mode 3840x2160@60Hz position 3840,0
  output &#34;Dell Inc. DELL G3223Q 82X70P3&#34; mode 3840x2160@60Hz position 0,0
  output &#34;eDP-1&#34; disable
}

profile undocked {
  output &#34;eDP-1&#34; enable scale 1.100000
}
</code></pre><p>To set this up using NixOS/home-manager we can do something like this say a file called <code>kanshi.nix</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">services</span><span class="o">.</span><span class="n">kanshi</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">systemdTarget</span> <span class="o">=</span> <span class="s2">&#34;hyprland-session.target&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">profiles</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">undocked</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">outputs</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">          <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">criteria</span> <span class="o">=</span> <span class="s2">&#34;eDP-1&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">scale</span> <span class="o">=</span> <span class="mi">1</span><span class="mf">.1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">status</span> <span class="o">=</span> <span class="s2">&#34;enable&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">          <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">];</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">home_office</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">outputs</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">          <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">criteria</span> <span class="o">=</span> <span class="s2">&#34;GIGA-BYTE TECHNOLOGY CO., LTD. Gigabyte M32U 21351B000087&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">position</span> <span class="o">=</span> <span class="s2">&#34;3840,0&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">mode</span> <span class="o">=</span> <span class="s2">&#34;3840x2160@60Hz&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">          <span class="p">}</span>
</span></span><span class="line"><span class="cl">          <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">criteria</span> <span class="o">=</span> <span class="s2">&#34;Dell Inc. DELL G3223Q 82X70P3&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">position</span> <span class="o">=</span> <span class="s2">&#34;0,0&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">mode</span> <span class="o">=</span> <span class="s2">&#34;3840x2160@60Hz&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">          <span class="p">}</span>
</span></span><span class="line"><span class="cl">          <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">criteria</span> <span class="o">=</span> <span class="s2">&#34;eDP-1&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">status</span> <span class="o">=</span> <span class="s2">&#34;disable&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">          <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">];</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>I was unable to get it work until I added this line <code>systemdTarget = &quot;hyprland-session.target&quot;;</code>, this is the
Systemd target to bind to. Since we are using hyprland we need to attach to hyprland the default is sway.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Part 1: NixOS as part of your Development Workflow</title>
      <link>https://haseebmajid.dev/posts/2023-07-20-nixos-as-part-of-your-development-workflow/</link>
      <pubDate>Thu, 20 Jul 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-07-20-nixos-as-part-of-your-development-workflow/</guid>
      <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;details
  class=&#34;notice info&#34;
  open=&#34;true&#34;
&gt;
    &lt;summary class=&#34;notice-title&#34;&gt;Dev Machine&lt;/summary&gt;
  
  &lt;p&gt;My main machine for development at the moment is a 12th Generation Intel Framework Laptop.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;

&lt;/details&gt;

&lt;p&gt;This series has been inspired by &lt;a href=&#34;https://www.joshmedeski.com/guides/dev-workflow-intro/&#34;&gt;Dev Workflow Intro by Josh Medeski&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In this series of posts, I will go over how I have set up my developer workflow
and explain why I have made certain decisions and why I use certain tools. This series aim to make it less daunting for you to start your journey
on improving your developer workflow. Or share with you tools that you made not
have heard of. Most of the tools I used I discovered in videos/blogs talking about
developer workflows.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">Dev Machine</summary>
  
  <p>My main machine for development at the moment is a 12th Generation Intel Framework Laptop.</p>
<p></p>

</details>

<p>This series has been inspired by <a href="https://www.joshmedeski.com/guides/dev-workflow-intro/">Dev Workflow Intro by Josh Medeski</a>.</p>
<p>In this series of posts, I will go over how I have set up my developer workflow
and explain why I have made certain decisions and why I use certain tools. This series aim to make it less daunting for you to start your journey
on improving your developer workflow. Or share with you tools that you made not
have heard of. Most of the tools I used I discovered in videos/blogs talking about
developer workflows.</p>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">My Opinion</summary>
  
  This section is entirely my opinion you are free to use whichever operating system you wish.
Most of my developer workflow can be replicated on any operating system.
</details>

<h2 id="operating-system-os">Operating System (OS)</h2>
<p>In this post, we will go over what I think is the first part of your development workflow which is your operating system.
I use NixOS, so we will go over why I use NixOS but first, let me explain why I don&rsquo;t use Windows or MacOS.</p>
<h3 id="tldr">tl:dr;</h3>
<p>If you like tinkering Linux is the OS for you.</p>
<ul>
<li>Linux is open-source</li>
<li>Linux is super customise</li>
<li>Linux is performant</li>
<li>No telemetry on Linux
<ul>
<li>Improved telemetry</li>
</ul>
</li>
</ul>
<h3 id="why-not-windows">Why not Windows?</h3>
<p>I have used both Windows and MacOS for development. Windows, talking specifically about 11, especially with WSL
works pretty well, you can use the same commands you do on Linux. However, I find the operating system to be
&ldquo;bloated&rdquo;. One example is searching for files seems to take a lot longer, I think there are ways to speed this up
but the last time I used Windows search it just never worked as well as I want it to.</p>
<p>Another example I find opening
applications to just be a lot snappier on Linux as compared with Windows. It is likely not enough to matter to most but
it annoys me enough and just adds to why I find Linux nicer to use. The final straw is all the telemetry Windows
collects about you, again which you can turn off using the registry. I like the idea of using open-source tools where
I can, seeing as I rely on so much open-source for my day-to-day.</p>
<h3 id="why-not-macos">Why not MacOS?</h3>
<p>I&rsquo;ve had to use MacOS for work a few times and each time I get annoyed mainly at the lack of customisability.
I find it very hard to get it to work exactly how I want. I also wasn&rsquo;t able to use Yabai as a tiling window manager,
which didn&rsquo;t help. You can run Linux on any hardware, whereas officially MacOS will only run on select devices.</p>
<p>I don&rsquo;t do any IOS development so I don&rsquo;t need a MacOS device to do that.
As a slight aside I think MacBooks are also very expensive, I have never bought one for myself. But some people love
their Macs and plenty of developers use them, swear by them.</p>
<h3 id="why-linux">Why Linux?</h3>
<p>I like Linux because it is open-source, free to use and secure. It comes in various flavours/distributions so the
desktop experience can vary a lot. However, most flavours will allow you to run most desktop environments, like gnome
can be used with Ubuntu, POP_!OS, Arch or even NixOS.</p>
<p>Most Linux distributions also don&rsquo;t collect any telemetry about the user and there is far more private than other
OS&rsquo;s. The main reason I Linux is choice and freedom, you can use tinker the OS to your liking. There are lots
of desktop environments and window managers which can be tweaked to your liking.</p>
<p>In my case, I am currently using Hyprland with some of my custom config, as my tiling window manager.
Most of which I have configured, such as which tool to use for notifications. We will cover more of this in another
post.</p>
<h3 id="why-nixos">Why NixOS?</h3>
<p>Ok so now we have discussed why I use Linux as my OS there are many distributions. Just in the last 5 years or so I have
used Ubuntu, Fedora, POP!_OS, Arch and finally NixOS. So why am I currently using NixOS, well the answer is simple?
The main feature of NixOS is that you can declare almost the entire state of your machine declaratively in code.
declaratively means we define the end state in code, i.e. what packages we want to be installed then NixOS works out how to
get to that state.</p>
<p>So your desktop environment is reproducible. I could easily share config between multiple devices.</p>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">Why I moved to NixOS</summary>
  
  You can read about <a href="/posts/2023-06-25-why-i-moved-to-nixos/">my article</a>, for more reasons to move to NixOs.
</details>

<h2 id="how-to-set-up-nixos">How to set up NixOS?</h2>
<p>In this section, we will take a look at how we can install NixOS, or rather how I set up NixOS on my devices.
First, we need to burn a USB with our the <a href="https://nixos.org/download.html">NixOS ISO</a>.</p>
<p>Then open a terminal and run the following commands, I want to set up an encrypted disk using btrfs. Mainly so that
we can do <a href="https://mt-caret.github.io/blog/posts/2020-06-29-optin-state.html">opt-in persistance</a>, such that our OS
is erased and rebuilt on startup, to avoid configuration drift between our declarative code and system state.</p>
<p>This script is taken from <a href="https://gist.github.com/hadilq/a491ca53076f38201a8aa48a0c6afef5">here</a>. We
won&rsquo;t use the GUI installer (though we could also do that). I want specific btrfs subvolumes, which we can think of
as logical partitions.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">DISK</span><span class="o">=</span>/dev/nvme0n1
</span></span><span class="line"><span class="cl"><span class="c1"># Format the EFI partition</span>
</span></span><span class="line"><span class="cl">mkfs.vfat -n boot <span class="s2">&#34;</span><span class="nv">$DISK</span><span class="s2">&#34;</span>p1
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">cryptsetup --verify-passphrase -v luksFormat <span class="s2">&#34;</span><span class="nv">$DISK</span><span class="s2">&#34;</span>p2
</span></span><span class="line"><span class="cl">cryptsetup open <span class="s2">&#34;</span><span class="nv">$DISK</span><span class="s2">&#34;</span>p2 enc
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Creat the swap inside the encrypted partition</span>
</span></span><span class="line"><span class="cl">pvcreate /dev/mapper/enc
</span></span><span class="line"><span class="cl">vgcreate lvm /dev/mapper/enc
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">lvcreate --size 32G --name swap lvm
</span></span><span class="line"><span class="cl"> lvcreate --extents 100%FREE --name root lvm
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">mkswap -L swap /dev/lvm/swap
</span></span><span class="line"><span class="cl">mkfs.btrfs -L nixos /dev/lvm/root
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">swapon /dev/lvm/swap
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Then create subvolumes</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">mount -t btrfs /dev/lvm/root /mnt
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># We first create the subvolumes outlined above:</span>
</span></span><span class="line"><span class="cl">btrfs subvolume create /mnt/root
</span></span><span class="line"><span class="cl">btrfs subvolume create /mnt/home
</span></span><span class="line"><span class="cl">btrfs subvolume create /mnt/nix
</span></span><span class="line"><span class="cl">btrfs subvolume create /mnt/persist
</span></span><span class="line"><span class="cl">btrfs subvolume create /mnt/log
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># We then take an empty *readonly* snapshot of the root subvolume,</span>
</span></span><span class="line"><span class="cl"><span class="c1"># which we&#39;ll eventually rollback to on every boot.</span>
</span></span><span class="line"><span class="cl">btrfs subvolume snapshot -r /mnt/root /mnt/root-blank
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">umount /mnt
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Mount the directories</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">mount -o <span class="nv">subvol</span><span class="o">=</span>root,compress<span class="o">=</span>zstd,noatime /dev/lvm/root /mnt
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">mkdir /mnt/home
</span></span><span class="line"><span class="cl">mount -o <span class="nv">subvol</span><span class="o">=</span>home,compress<span class="o">=</span>zstd,noatime /dev/lvm/root /mnt/home
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">mkdir /mnt/nix
</span></span><span class="line"><span class="cl">mount -o <span class="nv">subvol</span><span class="o">=</span>nix,compress<span class="o">=</span>zstd,noatime /dev/lvm/root /mnt/nix
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">mkdir /mnt/persist
</span></span><span class="line"><span class="cl">mount -o <span class="nv">subvol</span><span class="o">=</span>persist,compress<span class="o">=</span>zstd,noatime /dev/lvm/root /mnt/persist
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">mkdir -p /mnt/var/log
</span></span><span class="line"><span class="cl">mount -o <span class="nv">subvol</span><span class="o">=</span>log,compress<span class="o">=</span>zstd,noatime /dev/lvm/root /mnt/var/log
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># don&#39;t forget this!</span>
</span></span><span class="line"><span class="cl">mkdir /mnt/boot
</span></span><span class="line"><span class="cl">mount <span class="s2">&#34;</span><span class="nv">$DISK</span><span class="s2">&#34;</span>p1 /mnt/boot
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">nixos-generate-config --root /mnt
</span></span></code></pre></div><p>Then in our <code>/mnt/etc/nixos/configuration.nix</code> we edit it to look something like this. Just to give us a simple gnome
desktop environment with a few basic packages installed (in a future post we will install more).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="c1"># Edit this configuration file to define what should be installed on</span>
</span></span><span class="line"><span class="cl"><span class="c1"># your system.  Help is available in the configuration.nix(5) man page</span>
</span></span><span class="line"><span class="cl"><span class="c1"># and in the NixOS manual (accessible by running ‘nixos-help’).</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">{</span> <span class="n">config</span><span class="o">,</span> <span class="n">pkgs</span><span class="o">,</span> <span class="o">...</span> <span class="p">}:</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">imports</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl">    <span class="p">[</span> <span class="c1"># Include the results of the hardware scan.</span>
</span></span><span class="line"><span class="cl">      <span class="sr">./hardware-configuration.nix</span>
</span></span><span class="line"><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">boot</span><span class="o">.</span><span class="n">kernelPackages</span> <span class="o">=</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">linuxPackages_latest</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">boot</span><span class="o">.</span><span class="n">supportedFilesystems</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;btrfs&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="n">hardware</span><span class="o">.</span><span class="n">enableAllFirmware</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">nixpkgs</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">allowUnfree</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Use the systemd-boot EFI boot loader.</span>
</span></span><span class="line"><span class="cl">  <span class="n">boot</span><span class="o">.</span><span class="n">loader</span><span class="o">.</span><span class="n">systemd-boot</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">boot</span><span class="o">.</span><span class="n">loader</span><span class="o">.</span><span class="n">efi</span><span class="o">.</span><span class="n">canTouchEfiVariables</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">boot</span><span class="o">.</span><span class="n">initrd</span><span class="o">.</span><span class="n">luks</span><span class="o">.</span><span class="n">devices</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">root</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Use https://nixos.wiki/wiki/Full_Disk_Encryption</span>
</span></span><span class="line"><span class="cl">        <span class="n">device</span> <span class="o">=</span> <span class="s2">&#34;/dev/disk/by-uuid/TO find this hash use lsblk -f. It&#39;s the UUID of nvme0n1p2&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">preLVM</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">networking</span><span class="o">.</span><span class="n">hostName</span> <span class="o">=</span> <span class="s2">&#34;framework&#34;</span><span class="p">;</span> <span class="c1"># Define your hostname.</span>
</span></span><span class="line"><span class="cl">  <span class="n">networking</span><span class="o">.</span><span class="n">networkmanager</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># networking.wireless.enable = true;  # Enables wireless support via wpa_supplicant.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Set your time zone.</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># time.timeZone = &#34;Europe/Amsterdam&#34;;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># The global useDHCP flag is deprecated, therefore explicitly set to false here.</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># Per-interface useDHCP will be mandatory in the future, so this generated config</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># replicates the default behaviour.</span>
</span></span><span class="line"><span class="cl">  <span class="n">networking</span><span class="o">.</span><span class="n">useDHCP</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">services</span><span class="o">.</span><span class="n">xserver</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">services</span><span class="o">.</span><span class="n">xserver</span><span class="o">.</span><span class="n">displayManager</span><span class="o">.</span><span class="n">gdm</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">services</span><span class="o">.</span><span class="n">xserver</span><span class="o">.</span><span class="n">desktopManager</span><span class="o">.</span><span class="n">gnome</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Define a user account. Don&#39;t forget to set a password with ‘passwd’.</span>
</span></span><span class="line"><span class="cl">  <span class="n">users</span><span class="o">.</span><span class="n">users</span><span class="o">.</span><span class="n">haseeb</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">isNormalUser</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">extraGroups</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;wheel&#34;</span> <span class="p">];</span> <span class="c1"># Enable ‘sudo’ for the user.</span>
</span></span><span class="line"><span class="cl">    <span class="n">hashedPassword</span> <span class="o">=</span> <span class="s2">&#34;Run mkpasswd -m sha-512 to generate it&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># List packages installed in system profile. To search, run:</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># $ nix search wget</span>
</span></span><span class="line"><span class="cl">  <span class="n">environment</span><span class="o">.</span><span class="n">systemPackages</span> <span class="o">=</span> <span class="k">with</span> <span class="n">pkgs</span><span class="p">;</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="n">wget</span> <span class="n">vim</span> <span class="n">git</span> <span class="n">mkpasswd</span>
</span></span><span class="line"><span class="cl">    <span class="n">firefox</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span></code></pre></div><p>Run <code>sudo nixos-generate-config --root /mnt</code> to create a hardware configuration file.
Then edit our <code>/mnt/etc/nixos/hardware-configuration.nix</code>. This is where we utilise the labels that we defined above.
You can only change the file system in your hardware-configuration.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="c1"># Do not modify this file!  It was generated by ‘nixos-generate-config’</span>
</span></span><span class="line"><span class="cl"><span class="c1"># and may be overwritten by future invocations.  Please make changes</span>
</span></span><span class="line"><span class="cl"><span class="c1"># to /etc/nixos/configuration.nix instead.</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span> <span class="n">config</span><span class="o">,</span> <span class="n">lib</span><span class="o">,</span> <span class="n">pkgs</span><span class="o">,</span> <span class="n">modulesPath</span><span class="o">,</span> <span class="o">...</span> <span class="p">}:</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">imports</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl">    <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="p">(</span><span class="n">modulesPath</span> <span class="o">+</span> <span class="s2">&#34;/installer/scan/not-detected.nix&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">boot</span><span class="o">.</span><span class="n">initrd</span><span class="o">.</span><span class="n">availableKernelModules</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;xhci_pci&#34;</span> <span class="s2">&#34;thunderbolt&#34;</span> <span class="s2">&#34;nvme&#34;</span> <span class="s2">&#34;uas&#34;</span> <span class="s2">&#34;usb_storage&#34;</span> <span class="s2">&#34;sd_mod&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="n">boot</span><span class="o">.</span><span class="n">initrd</span><span class="o">.</span><span class="n">kernelModules</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;dm-snapshot&#34;</span> <span class="s2">&#34;amdgpu&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="n">boot</span><span class="o">.</span><span class="n">kernelModules</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;kvm-intel&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="n">boot</span><span class="o">.</span><span class="n">extraModulePackages</span> <span class="o">=</span> <span class="p">[</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">fileSystems</span><span class="o">.</span><span class="s2">&#34;/&#34;</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">device</span> <span class="o">=</span> <span class="s2">&#34;/dev/disk/by-label/nixos&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">fsType</span> <span class="o">=</span> <span class="s2">&#34;btrfs&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">options</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;subvol=root&#34;</span> <span class="s2">&#34;compress=zstd&#34;</span> <span class="s2">&#34;noatime&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">fileSystems</span><span class="o">.</span><span class="s2">&#34;/home&#34;</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">device</span> <span class="o">=</span> <span class="s2">&#34;/dev/disk/by-label/nixos&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">fsType</span> <span class="o">=</span> <span class="s2">&#34;btrfs&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">options</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;subvol=home&#34;</span> <span class="s2">&#34;compress=zstd&#34;</span> <span class="s2">&#34;noatime&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">fileSystems</span><span class="o">.</span><span class="s2">&#34;/nix&#34;</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">device</span> <span class="o">=</span> <span class="s2">&#34;/dev/disk/by-label/nixos&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">fsType</span> <span class="o">=</span> <span class="s2">&#34;btrfs&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">options</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;subvol=nix&#34;</span> <span class="s2">&#34;compress=zstd&#34;</span> <span class="s2">&#34;noatime&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">fileSystems</span><span class="o">.</span><span class="s2">&#34;/persist&#34;</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">device</span> <span class="o">=</span> <span class="s2">&#34;/dev/disk/by-label/nixos&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">fsType</span> <span class="o">=</span> <span class="s2">&#34;btrfs&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">options</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;subvol=persist&#34;</span> <span class="s2">&#34;compress=zstd&#34;</span> <span class="s2">&#34;noatime&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">      <span class="n">neededForBoot</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">fileSystems</span><span class="o">.</span><span class="s2">&#34;/var/log&#34;</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">device</span> <span class="o">=</span> <span class="s2">&#34;/dev/disk/by-label/nixos&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">fsType</span> <span class="o">=</span> <span class="s2">&#34;btrfs&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">options</span> <span class="o">=</span> <span class="p">[</span> <span class="s2">&#34;subvol=log&#34;</span> <span class="s2">&#34;compress=zstd&#34;</span> <span class="s2">&#34;noatime&#34;</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">      <span class="n">neededForBoot</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">fileSystems</span><span class="o">.</span><span class="s2">&#34;/boot&#34;</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">device</span> <span class="o">=</span> <span class="s2">&#34;/dev/disk/by-label/boot&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">fsType</span> <span class="o">=</span> <span class="s2">&#34;vfat&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">swapDevices</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">device</span> <span class="o">=</span> <span class="s2">&#34;/dev/disk/by-label/swap&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Enables DHCP on each ethernet and wireless interface. In case of scripted networking</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># (the default) this is the recommended approach. When using systemd-networkd it&#39;s</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># still possible to use this option, but it&#39;s recommended to use it in conjunction</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># with explicit per-interface declarations with `networking.interfaces.&lt;interface&gt;.useDHCP`.</span>
</span></span><span class="line"><span class="cl">  <span class="n">networking</span><span class="o">.</span><span class="n">useDHCP</span> <span class="o">=</span> <span class="n">lib</span><span class="o">.</span><span class="n">mkDefault</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># networking.interfaces.wlp166s0.useDHCP = lib.mkDefault true;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">nixpkgs</span><span class="o">.</span><span class="n">hostPlatform</span> <span class="o">=</span> <span class="n">lib</span><span class="o">.</span><span class="n">mkDefault</span> <span class="s2">&#34;x86_64-linux&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">powerManagement</span><span class="o">.</span><span class="n">cpuFreqGovernor</span> <span class="o">=</span> <span class="n">lib</span><span class="o">.</span><span class="n">mkDefault</span> <span class="s2">&#34;powersave&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">hardware</span><span class="o">.</span><span class="n">cpu</span><span class="o">.</span><span class="n">intel</span><span class="o">.</span><span class="n">updateMicrocode</span> <span class="o">=</span> <span class="n">lib</span><span class="o">.</span><span class="n">mkDefault</span> <span class="n">config</span><span class="o">.</span><span class="n">hardware</span><span class="o">.</span><span class="n">enableRedistributableFirmware</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Then to install the OS properly on our nvme ssd run <code>sudo nixos-install</code>.</p>
<h2 id="whats-next">What&rsquo;s Next?</h2>
<p>In the next post, we will go over how we can set up our window manager (Hyprland). This will also show us how we can use
NixOS to configure our system.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Part 0: Hardware as Part of Your Development Workflow</title>
      <link>https://haseebmajid.dev/posts/2023-07-18-my-hardware-as-part-of-my-development-workflow/</link>
      <pubDate>Tue, 18 Jul 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-07-18-my-hardware-as-part-of-my-development-workflow/</guid>
      <description>&lt;p&gt;This series has been inspired by &lt;a href=&#34;https://www.joshmedeski.com/guides/dev-workflow-intro/&#34;&gt;Dev Workflow Intro by Josh Medeski&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is sort of a precursor to my series about how I have set up my development workflow. Hopefully, you will be able to
pick a few tips and tricks you can add to your development workflow to make it more efficient. Or even give me tips
on how I can make my workflow more efficient.&lt;/p&gt;
&lt;p&gt;In this post we will go over arguably the least important, but at the same time quite important, part of our workflow.
Because in theory, we can run the same development workflow on lots of different types of hardware. I don&amp;rsquo;t
necessarily need a mechanical keyboard or a monitor I can just use a keyboard.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>This series has been inspired by <a href="https://www.joshmedeski.com/guides/dev-workflow-intro/">Dev Workflow Intro by Josh Medeski</a>.</p>
<p>This is sort of a precursor to my series about how I have set up my development workflow. Hopefully, you will be able to
pick a few tips and tricks you can add to your development workflow to make it more efficient. Or even give me tips
on how I can make my workflow more efficient.</p>
<p>In this post we will go over arguably the least important, but at the same time quite important, part of our workflow.
Because in theory, we can run the same development workflow on lots of different types of hardware. I don&rsquo;t
necessarily need a mechanical keyboard or a monitor I can just use a keyboard.</p>
<p>However, if you work from the same environment i.e. your desk at home or work you also want to make your setup as
comfortable as possible. With a nice monitor, a comfortable keyboard and mouse.</p>
<h2 id="introduction">Introduction</h2>
<p>There are 100s of different machines and configurations you could use for development. I can of course not list them all.
Instead for this &ldquo;part 0&rdquo;, I will just go over my specific setup what&rsquo;s good about and what issues I have with it.</p>
<p>I reckon most developers use a laptop, especially for work, for each of my jobs I have been given a work laptop and
have done all of my development on it. Whether I use a laptop or a desktop I like to treat them the same.
For most of my personal development at home, I have used a desktop, which also doubled as my gaming machine. Recently
I have moved to a laptop, a framework 12th generation, to do all my development (and a bit of gaming).</p>
<h2 id="laptop-vs-desktop">Laptop vs Desktop</h2>
<p>The main reason for moving to a laptop was to be able to have a portable development environment. If I wanted to travel,
I found it took some time to set up my laptop. Whether that be making sure to git pull all the repos I wanted to work on,
essentially I had to make sure my laptop was in sync with my desktop. Sometimes even updating the system could take
a while,  using just my laptop I could solve this problem.</p>
<h2 id="laptop-setup">Laptop Setup</h2>
<p>At the moment I am using a 12th-generation framework laptop as my main development machine, alongside a 2019 MacBook for
work. However, if I had it my way I wouldn&rsquo;t be using that &#x1f622;!</p>
<p>I like to use my laptops in a way that still gives me a &ldquo;desktop-style experience&rdquo;, what this means is I dock my laptop
in a stand (closed lid). I then connect it to a Thunderbolt dock, where the dock is connected to my monitors,
2 x 32-inch 4k monitors, mechanical keyboard, mouse etc. So if your laptop supports TB3/TB4 you can setup your
environment with just a single cable connecting to the laptop. When I need to be mobile, I can just disconnect the
laptop from that single cable and go on about my day.</p>
<h3 id="egpu">eGPU</h3>
<p>In terms of trying to game on my laptop or heavier workloads where I want to use a GPU, I am going to attempt to use
an external GPU enclosure and put one of my graphics cards in there. For gaming I have got VFIO GPU passthrough, using
a Windows VM with libvirt. However, I haven&rsquo;t been able to get any game except Guild War 2 to run properly.</p>
<h3 id="cons">Cons</h3>
<ul>
<li>The laptop is very loud even under moderate load, fans start to spin</li>
<li>Not as powerful as my desktop</li>
<li>Cannot game on it properly</li>
</ul>
<h2 id="specific-setup">Specific Setup</h2>
<p>Going over the specific hardware I use, note there will be lots of alternatives these are just the ones I use.</p>
<h3 id="monitors">Monitors</h3>
<p>I have to 32-inch 4k displays, I have them side by side horizontally. I wanted them to use IPS panels, to have
better viewing angles. My two monitors are</p>
<ul>
<li>Gigabyte 32 M32U
<ul>
<li>144HZ monitor G-Sync and FreeSync compatible, mostly for gaming features</li>
</ul>
</li>
<li>Dell G3223Q</li>
</ul>
<p><img
        loading="lazy"
        src="/posts/2023-07-18-my-hardware-as-part-of-my-development-workflow/images/monitors.jpg"
        type=""
        alt="Monitors"
        
      /></p>
<p>Please ignore the mess of cables, I&rsquo;d like to pretend my desk is tidier most of the time :rolling_on_the_floor_laughing:</p>
<h3 id="keyboard">Keyboard</h3>
<p>I am using a mechanical keyboard like most other developers, I think they provide a better typing experience as compared
with normal membrane keyboards. However, if you want a cheap keyboard that works really well I really like the Logitech
k120.</p>
<p>However, I am currently using the <a href="https://www.keychron.com/products/keychron-q1-pro-qmk-via-wireless-custom-mechanical-keyboard">Keychron Q1 Pro</a>
with Kalih Box White switches and Dracula GMK keycaps.</p>
<p>Previously I have used Corsair keyboards but I really like the Keychron ones as the switches are hot-swappable, so a
single switch breaks it&rsquo;s really easy to replace. Whereas with the Corsair ones I had the switches were soldered onto
the board meaning you need to do some soldering if a switch broke. It&rsquo;s essentially a lot easier to change switches
and other parts of the keyboard with the Keychrone ones. They are also similarly priced so I see no reason not to go
with it.</p>
<p>This specific version comes with a knob, which I use to control the volume on my machine and clicking it mutes the sound
which is nice to quickly adjust the volume from audio sources if it gets too loud or is too quiet. It can also be used
wirelessly or wired you can decide, so there is a level of flexibility.</p>
<p><img
        loading="lazy"
        src="/posts/2023-07-18-my-hardware-as-part-of-my-development-workflow/images/keyboard.jpg"
        type=""
        alt="Keyboard"
        
      /></p>
<p>Once again please ignore the mess of cables, I&rsquo;d like to pretend my desk is tidier most
of the time :rolling_on_the_floor_laughing:</p>
<h3 id="mouse">Mouse</h3>
<p>I use a <a href="https://www.logitechg.com/en-gb/products/gaming-mice/g502-x-wireless-lightforce.910-006190.html">Logitech G502 X</a>
, with a Logitech powerplay to charge my wireless mouse, automagically. I don&rsquo;t like having too many wires, so having
a wireless mouse is nice. I know there is the same number of wires because of the powerplay but the wire is much more
out of the way and easier to cable manage.</p>
<h3 id="thunderbolt-dock">Thunderbolt Dock</h3>
<p>I am using the <a href="https://www.corsair.com/uk/en/p/hubs-docks/cu-9000001-eu/tbt100-thunderbolt-3-dock-eu-cu-9000001-eu">Corsair TB100 dock</a>.
To try to have a &ldquo;single cable experience&rdquo;. I plug everything into the dock then the single cable from the dock
into my laptop, providing a nice UX.</p>
<h3 id="other">Other</h3>
<ul>
<li>Dual Laptop Stand
<ul>
<li>One for work laptop (MacBook)</li>
<li>Another for personal laptop (Framework)</li>
<li>It&rsquo;s next to the Thunderbolt dock</li>
</ul>
</li>
<li>Webcam: <a href="https://www.logitech.com/en-gb/products/webcams/brio-4k-hdr-webcam.960-001106.html">Logitech Brio</a></li>
<li>Headset: <a href="https://www.corsair.com/uk/en/p/gaming-headsets/ca-9011185-eu/virtuoso-rgb-wireless-high-fidelity-gaming-headset-carbon-eu-ca-9011185-eu">Corsair Virtuoso</a>
<ul>
<li>A bit too gamery looking, worse well enough comfortable after I replaced the ear pads</li>
</ul>
</li>
<li>Yubikey: <a href="https://www.yubico.com/gb/product/yubikey-5-series/yubikey-5-nfc/">5 NFC model</a>
<ul>
<li>Use it for 2FA, and convenience like if plugged in auto allow login and sudo commands without a password</li>
</ul>
</li>
</ul>
<p>That&rsquo;s it a quick look at some of the important bits of hardware I use. As I say most of this can be swapped out with
other components. Especially a desktop with a laptop. The next part in this series will go over what operating system
I use and why!</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>My Dotfiles III</title>
      <link>https://haseebmajid.dev/posts/2023-07-15-my-dotfiles-iii/</link>
      <pubDate>Sat, 15 Jul 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-07-15-my-dotfiles-iii/</guid>
      <description>&lt;p&gt;My latest iteration of my dotfiles, where I am now using a Laptop as my main development machine (Framework).
I&amp;rsquo;m also using Hyprland as my window manager. Most importantly using NixOS and home-manager to declaratively define
the state of my machine i.e. what packages to install, dotfiles.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CURRENTLY A WIP&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&amp;#x1f3e0; My dotfiles repo, setup using nixos/home-manager&lt;/p&gt;
&lt;h2 id=&#34;install&#34;&gt;Install&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;#x1f525; I wouldn&amp;rsquo;t recommend just blinding using my dotfiles. They are setup for my specific use-case.
I think you&amp;rsquo;re off using this repo as reference to create your own dotfiles.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>My latest iteration of my dotfiles, where I am now using a Laptop as my main development machine (Framework).
I&rsquo;m also using Hyprland as my window manager. Most importantly using NixOS and home-manager to declaratively define
the state of my machine i.e. what packages to install, dotfiles.</p>
<p><strong>CURRENTLY A WIP</strong></p>
<p>&#x1f3e0; My dotfiles repo, setup using nixos/home-manager</p>
<h2 id="install">Install</h2>
<blockquote>
<p>&#x1f525; I wouldn&rsquo;t recommend just blinding using my dotfiles. They are setup for my specific use-case.
I think you&rsquo;re off using this repo as reference to create your own dotfiles.</p>
</blockquote>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone git@github.com:hmajid2301/dotfiles.git ~/dotfiles/
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> dotfiles
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">nix develop
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># To build system configuration</span>
</span></span><span class="line"><span class="cl">sudo nixos-rebuild --flake .#framework
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># To build user configuration</span>
</span></span><span class="line"><span class="cl">home-manager --flake .#framework
</span></span></code></pre></div><p>You can read more about my dotfiles and development workflows on my <a href="https://haseebmajid.dev/series/my-development-workflow/">blog here</a> (#ShamelessPlug).</p>
<blockquote>
<p>Note my dotfiles are almost always changing!</p>
</blockquote>
<h2 id="features">Features</h2>
<p>Some features of my dotfiles:</p>
<ul>
<li>Structured to allow multiple <strong>NixOS configurations</strong>, including <strong>desktop</strong>, <strong>laptop</strong></li>
<li><strong>Declarative</strong> config including <strong>themes</strong>, <strong>wallpapers</strong> and <strong>nix-colors</strong></li>
<li><strong>Opt-in persistance</strong> through impermanence + blank snapshot</li>
<li>Delete files on boot</li>
<li><strong>Encrypted btrfs partition</strong></li>
<li><strong>sops-nix</strong> for secrets management</li>
<li>Different environments like <strong>hyprland</strong> and <strong>gnome</strong></li>
<li>Laptop setup with eGPU and <strong>vfio</strong> for playing games on windows</li>
</ul>
<h2 id="structure">Structure</h2>
<ul>
<li><code>flake.nix</code>: Entrypoint for hosts and home configurations</li>
<li><code>nixos</code>:
<ul>
<li><code>global</code>: Configurations that are globally applied to all my machines</li>
<li><code>optional</code>: Configurations that some of my machines use</li>
</ul>
</li>
<li><code>hosts</code>: NixOS Configurations, accessible via <code>nixos-rebuild --flake</code>.
<ul>
<li><code>framework</code>: Framework 12th gen laptop | Hyprland | eGPU 7900 XTX</li>
</ul>
</li>
<li><code>home-manager</code>: Most of my dotfiles configuration, user specific</li>
</ul>
<h2 id="devices">Devices</h2>
<p>Here is a list of the devices this repo is used to configure.</p>
<h3 id="framework-laptop">Framework Laptop</h3>
<p><img
        loading="lazy"
        src="/posts/2023-07-15-my-dotfiles-iii/images/neofetch.png"
        type=""
        alt="Neofetch"
        
      /></p>
<table>
  <thead>
      <tr>
          <th style="text-align: left">Type</th>
          <th style="text-align: center">Program</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left">Editor</td>
          <td style="text-align: center"><a href="https://neovim.io/">NeoVim</a></td>
      </tr>
      <tr>
          <td style="text-align: left">Launcher</td>
          <td style="text-align: center"><a href="https://github.com/davatorium/rofi">Rofi</a></td>
      </tr>
      <tr>
          <td style="text-align: left">Shell</td>
          <td style="text-align: center"><a href="https://fishshell.com/">Fish</a></td>
      </tr>
      <tr>
          <td style="text-align: left">Status Bar</td>
          <td style="text-align: center"><a href="https://github.com/Alexays/Waybar">Waybar</a></td>
      </tr>
      <tr>
          <td style="text-align: left">Terminal</td>
          <td style="text-align: center"><a href="https://github.com/alacritty/alacritty">Alacritty</a></td>
      </tr>
      <tr>
          <td style="text-align: left">Window Manager</td>
          <td style="text-align: center"><a href="https://hyprland.org/">Hyprland</a></td>
      </tr>
  </tbody>
</table>
<ul>
<li>OS: NixOS</li>
<li>WM: Hyprland</li>
<li>Shell: Fish
<ul>
<li>Prompt: <a href="https://starship.rs/">Starship</a></li>
</ul>
</li>
<li>Terminal: Alacritty
<ul>
<li>Editor: Neovim (using <a href="https://www.lazyvim.org">LazyVim</a> config)</li>
</ul>
</li>
<li>Colorscheme: <a href="https://github.com/catppuccin">Catppuccin for EVERYTHING!!!</a></li>
<li>Fonts: <a href="https://www.monolisa.dev/">Mono Lisa</a></li>
</ul>
<p><img
        loading="lazy"
        src="/posts/2023-07-15-my-dotfiles-iii/images/wallpaper.png"
        type=""
        alt="wallpaper"
        
      />
<img
        loading="lazy"
        src="/posts/2023-07-15-my-dotfiles-iii/images/neovim.png"
        type=""
        alt="neovim"
        
      />
<img
        loading="lazy"
        src="/posts/2023-07-15-my-dotfiles-iii/images/monkeytype.png"
        type=""
        alt="monkeytype"
        
      /></p>
<h2 id="applications">Applications</h2>
<p>I basically just installed every package from <a href="https://github.com/ibraheemdev/modern-unix">Modern Unix</a>.</p>
<p>CLI tools that I use often include:</p>
<ul>
<li><a href="https://github.com/junegunn/fzf">fzf</a>: Fuzzy search tool
<ul>
<li>Especially for reverse search in my terminal with <a href="https://github.com/PatrickF1/fzf.fish">fish shell</a></li>
</ul>
</li>
<li><a href="https://github.com/ajeetdsouza/zoxide">zoxide</a>: Smarter cd tool, integrated well with fzf, nvim and tmux</li>
<li><a href="https://github.com/ogham/exa">exa</a>: A replacement for <code>ls</code> with better syntax highlighting</li>
<li><a href="https://github.com/BurntSushi/ripgrep">ripgrep</a>: A faster <code>grep</code></li>
<li><a href="https://github.com/denisidoro/navi">navi</a>: Interactive cheat sheet</li>
</ul>
<h3 id="tmux">Tmux</h3>
<p>I manage my projects using tmux sessions with the <a href="https://github.com/joshmedeski/t-smart-tmux-session-manager">tmux smart session manager</a>.</p>
<p>Where I create a new session for each project I&rsquo;m working on and then jump between them.
Where a project might be:</p>
<ul>
<li>My Blog</li>
<li>My Dotfiles</li>
<li>Full stack application
<ul>
<li>A window for each project i.e. GUI and API</li>
</ul>
</li>
</ul>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://www.flaticon.com/free-icons/dot" title="dot icons">Dot icons created by Roundicons - Flaticon</a></li>
</ul>
<h3 id="inspired-by">Inspired By</h3>
<ul>
<li><a href="https://github.com/Misterio77/nix-config">https://github.com/Misterio77/nix-config</a> (Heavily!)</li>
<li><a href="https://github.dev/yurihikari/garuda-sway-config">https://github.dev/yurihikari/garuda-sway-config</a> (mostly scripts)</li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Update Nix Packages Using update-nix-fetchgit</title>
      <link>https://haseebmajid.dev/posts/2023-07-13-updating-nix-packages-using-update-nix-fetchgit/</link>
      <pubDate>Thu, 13 Jul 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-07-13-updating-nix-packages-using-update-nix-fetchgit/</guid>
      <description>&lt;p&gt;Recently I&amp;rsquo;ve been trying to work out how to update packages that I define declaratively in my Nix config.
I think I figured out how to do it using my Nix flake. By running &lt;code&gt;nix flake update&lt;/code&gt; and then
&lt;code&gt;sudo nixos-rebuild switch --flake ~/dotfiles#framework&lt;/code&gt; to update the packages.&lt;/p&gt;
&lt;p&gt;However, I have some plugins say for tmux which are defined like so:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;n&#34;&gt;t-smart-manager&lt;/span&gt; &lt;span class=&#34;err&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;pkgs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;tmuxPlugins&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;mkTmuxPlugin&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;n&#34;&gt;pluginName&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;t-smart-tmux-session-manager&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;n&#34;&gt;version&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;unstable-2023-06-05&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;n&#34;&gt;rtpFilePath&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;t-smart-tmux-session-manager.tmux&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;n&#34;&gt;src&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;pkgs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;fetchFromGitHub&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;owner&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;joshmedeski&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;repo&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;t-smart-tmux-session-manager&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;rev&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;0a4c77c5c3858814621597a8d3997948b3cdd35d&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;sha256&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;1dr5w02a0y84q2iw4jp1psxvkyj4g6pr87gc22syw1jd4ibkn925&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note the &lt;code&gt;fetchFromGitHub&lt;/code&gt; function, where we specify a specific git revision to get. I was looking at ways we could
update this automatically. Then I came across this tool &lt;a href=&#34;https://github.com/expipiplus1/update-nix-fetchgit&#34;&gt;update-nix-fetchgit&lt;/a&gt;.
When run it will update the &lt;code&gt;rev&lt;/code&gt;, &lt;code&gt;sha256&lt;/code&gt; and &lt;code&gt;version&lt;/code&gt; for us.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Recently I&rsquo;ve been trying to work out how to update packages that I define declaratively in my Nix config.
I think I figured out how to do it using my Nix flake. By running <code>nix flake update</code> and then
<code>sudo nixos-rebuild switch --flake ~/dotfiles#framework</code> to update the packages.</p>
<p>However, I have some plugins say for tmux which are defined like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl">  <span class="n">t-smart-manager</span> <span class="err">=</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">tmuxPlugins</span><span class="o">.</span><span class="n">mkTmuxPlugin</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">pluginName</span> <span class="o">=</span> <span class="s2">&#34;t-smart-tmux-session-manager&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">version</span> <span class="o">=</span> <span class="s2">&#34;unstable-2023-06-05&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">rtpFilePath</span> <span class="o">=</span> <span class="s2">&#34;t-smart-tmux-session-manager.tmux&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">src</span> <span class="o">=</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">fetchFromGitHub</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">owner</span> <span class="o">=</span> <span class="s2">&#34;joshmedeski&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">repo</span> <span class="o">=</span> <span class="s2">&#34;t-smart-tmux-session-manager&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">rev</span> <span class="o">=</span> <span class="s2">&#34;0a4c77c5c3858814621597a8d3997948b3cdd35d&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">sha256</span> <span class="o">=</span> <span class="s2">&#34;1dr5w02a0y84q2iw4jp1psxvkyj4g6pr87gc22syw1jd4ibkn925&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span></code></pre></div><p>Note the <code>fetchFromGitHub</code> function, where we specify a specific git revision to get. I was looking at ways we could
update this automatically. Then I came across this tool <a href="https://github.com/expipiplus1/update-nix-fetchgit">update-nix-fetchgit</a>.
When run it will update the <code>rev</code>, <code>sha256</code> and <code>version</code> for us.</p>
<p>Which will update various functions we use to fetch from git. All we need to do is install it then we can
do something like <code>fd .nix --exec update-nix-fetchgit</code>. Which will run the <code>update-nix-fetchgit</code> on all nix files in
our directory. The <code>fd</code> command (a <code>find</code> replacement) will find all nix files recursively in the current directory.</p>
<p>This should save us a lot of time doing it manually.</p>
<h2 id="devenv">devenv</h2>
<p>For my specific setup, I use <a href="devenv.sh/">devenv</a>. I have added it to my <code>devenv.nix</code> file, and set it up with <code>direnv</code>.
So it automatically loads a shell for me when I go navigate to the directory where my dotfiles are.</p>
<p>I will do a more detailed post on this setup, once I&rsquo;ve played around with it more. But by <code>devenv.nix</code> file looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span> <span class="n">pkgs</span><span class="o">,</span> <span class="o">...</span> <span class="p">}:</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># ....</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># https://devenv.sh/packages/</span>
</span></span><span class="line"><span class="cl">  <span class="n">packages</span> <span class="o">=</span> <span class="p">[</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">git</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">nixpkgs-fmt</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">update-nix-fetchgit</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># https://devenv.sh/languages/</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># languages.nix.enable = true;</span>
</span></span><span class="line"><span class="cl">  <span class="n">languages</span><span class="o">.</span><span class="n">nix</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># https://devenv.sh/pre-commit-hooks/</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># pre-commit.hooks.shellcheck.enable = true;</span>
</span></span><span class="line"><span class="cl">  <span class="c1">#</span>
</span></span><span class="line"><span class="cl">  <span class="n">pre-commit</span><span class="o">.</span><span class="n">hooks</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">nixpkgs-fmt</span><span class="o">.</span><span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># https://devenv.sh/processes/</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># processes.ping.exec = &#34;ping example.com&#34;;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># See full reference at https://devenv.sh/reference/options/</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>I install some packages when I run <code>devenv shell</code>, it makes the <code>update-nix-fetchgit</code> command available for me to run.
Or anyone else for that matter, in a new &ldquo;shell&rdquo;.</p>
<h2 id="future-actions">Future Actions</h2>
<ul>
<li>Run the command automatically</li>
<li>Use with neovim to update the line under cursor <a href="https://github.com/expipiplus1/update-nix-fetchgit#from-vim">see here</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Setting Up Tmux With Nix Home Manager</title>
      <link>https://haseebmajid.dev/posts/2023-07-10-setting-up-tmux-with-nix-home-manager/</link>
      <pubDate>Mon, 10 Jul 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-07-10-setting-up-tmux-with-nix-home-manager/</guid>
      <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;In this post I will show you how you can setup tmux (plugins) using the nix package manager, specifically using home-manager.
I will also show you how you can avoid an issue I encountered where tmux resurrect wasn&amp;rsquo;t working properly due to
plugin ordering.&lt;/p&gt;
&lt;p&gt;Typically we use the &lt;a href=&#34;https://github.com/tmux-plugins/tpm&#34;&gt;tmux plugin manager&lt;/a&gt; to manage our tmux plugins.
However, when I moved to NixOS, I wanted to move away from having lots of different ways of managing what is on my
system. For example, on my Arch Linux machine I had:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>In this post I will show you how you can setup tmux (plugins) using the nix package manager, specifically using home-manager.
I will also show you how you can avoid an issue I encountered where tmux resurrect wasn&rsquo;t working properly due to
plugin ordering.</p>
<p>Typically we use the <a href="https://github.com/tmux-plugins/tpm">tmux plugin manager</a> to manage our tmux plugins.
However, when I moved to NixOS, I wanted to move away from having lots of different ways of managing what is on my
system. For example, on my Arch Linux machine I had:</p>
<ul>
<li>Pacman/Yay</li>
<li>lazyvim</li>
<li>tpm</li>
</ul>
<p>However, it wasn&rsquo;t obvious how to do this. In my case, I decided to use <a href="https://nixos.wiki/wiki/Home_Manager">home-manager</a>
which is a tool we can use with
Nix to manage user config including dotfiles. For tmux specifically, there is a module we can leverage. So assuming
you already have home manager and Nix setup. We can create a new module, say called <code>tmux.nix</code></p>
<h2 id="tmuxnix">tmux.nix</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span> <span class="n">pkgs</span><span class="o">,</span> <span class="o">...</span> <span class="p">}:</span>
</span></span><span class="line"><span class="cl"><span class="k">let</span>
</span></span><span class="line"><span class="cl">  <span class="n">tmux-super-fingers</span> <span class="o">=</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">tmuxPlugins</span><span class="o">.</span><span class="n">mkTmuxPlugin</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">pluginName</span> <span class="o">=</span> <span class="s2">&#34;tmux-super-fingers&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">version</span> <span class="o">=</span> <span class="s2">&#34;unstable-2023-01-06&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="n">src</span> <span class="o">=</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">fetchFromGitHub</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">owner</span> <span class="o">=</span> <span class="s2">&#34;artemave&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">repo</span> <span class="o">=</span> <span class="s2">&#34;tmux_super_fingers&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">rev</span> <span class="o">=</span> <span class="s2">&#34;2c12044984124e74e21a5a87d00f844083e4bdf7&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">sha256</span> <span class="o">=</span> <span class="s2">&#34;sha256-cPZCV8xk9QpU49/7H8iGhQYK6JwWjviL29eWabuqruc=&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="k">in</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">programs</span><span class="o">.</span><span class="n">tmux</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">shell</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="si">${</span><span class="n">pkgs</span><span class="o">.</span><span class="n">fish</span><span class="si">}</span><span class="s2">/bin/fish&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">terminal</span> <span class="o">=</span> <span class="s2">&#34;tmux-256color&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">historyLimit</span> <span class="o">=</span> <span class="mi">100000</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">plugins</span> <span class="o">=</span> <span class="k">with</span> <span class="n">pkgs</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">plugin</span> <span class="o">=</span> <span class="n">tmux-super-fingers</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">          <span class="n">extraConfig</span> <span class="o">=</span> <span class="s2">&#34;set -g @super-fingers-key f&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="n">tmuxPlugins</span><span class="o">.</span><span class="n">better-mouse-mode</span>
</span></span><span class="line"><span class="cl">      <span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="n">extraConfig</span> <span class="o">=</span> <span class="s1">&#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">    &#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>In the file above there are a few key things I&rsquo;d like to discuss <code>programs.tmux.enable = true</code>, this automatically installs
tmux for us. We also set some other config options such as the shell to use and the history limit. Nix will automatically
add these to our tmux config file for us, which helps manage the config a bit easier in my opinion.</p>
<p>Next install tmux plugins available as a
<a href="https://search.nixos.org/packages?channel=unstable&amp;show=tmuxPlugins.better-mouse-mode&amp;from=0&amp;size=50&amp;sort=relevance&amp;type=packages&amp;query=better+mouse+mode">nix package</a> we can simply do:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"> <span class="n">plugins</span> <span class="err">=</span> <span class="k">with</span> <span class="n">pkgs</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="n">tmuxPlugins</span><span class="o">.</span><span class="n">better-mouse-mode</span>
</span></span><span class="line"><span class="cl">      <span class="p">];</span>
</span></span></code></pre></div><p>If however the plugin is not available, we can still install it like we did for tmux super fingers.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="n">tmux-super-fingers</span> <span class="err">=</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">tmuxPlugins</span><span class="o">.</span><span class="n">mkTmuxPlugin</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">pluginName</span> <span class="o">=</span> <span class="s2">&#34;tmux-super-fingers&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">version</span> <span class="o">=</span> <span class="s2">&#34;unstable-2023-01-06&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">src</span> <span class="o">=</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">fetchFromGitHub</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">owner</span> <span class="o">=</span> <span class="s2">&#34;artemave&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">repo</span> <span class="o">=</span> <span class="s2">&#34;tmux_super_fingers&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">rev</span> <span class="o">=</span> <span class="s2">&#34;2c12044984124e74e21a5a87d00f844083e4bdf7&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">sha256</span> <span class="o">=</span> <span class="s2">&#34;sha256-cPZCV8xk9QpU49/7H8iGhQYK6JwWjviL29eWabuqruc=&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>We tell home-manager where to get the plugin from on Git Hub,
which plugin we&rsquo;d like to install, and a specific revision (can be a tag or a commit sha). There is also a sha256, which
can be found when you run the <code>home-manager switch</code> command and leave the <code>sha256</code> field empty. It will initially fail
with the expected sha value which you can then copy over to the file.</p>
<p><img
        loading="lazy"
        src="/posts/2023-07-10-setting-up-tmux-with-nix-home-manager/images/example-sha.png"
        type=""
        alt="Example SHA"
        
      /></p>
<p>This sha is used to check if this derivation is already built by Nix and available to the machine, so we can skip
building it again. You can read more about it <a href="https://discourse.nixos.org/t/how-is-used-the-sha256-of-fetchfromgithub-exactly/4837">here</a>.</p>
<details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">Using Same SHA</summary>
  
  <p>When I was first setting it up, I didn&rsquo;t fully realise what the sha256 was used for and shared between multiple plugins.
What will happen is that the first plugin will be installed. Then Nix will see that SHA is already installed so won&rsquo;t
install the remaining plugins.</p>
<p><strong>So make sure you add your plugins one at a time, to avoid this.</strong></p>

</details>

<p>Then we add it to our list of plugins we want to be installed. We can also specify extra config related to that plugin,
using the <code>extraConfig</code> field.
which I think helps organise our config a bit more, like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">plugin</span> <span class="o">=</span> <span class="n">tmux-super-fingers</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">extraConfig</span> <span class="o">=</span> <span class="s2">&#34;set -g @super-fingers-key f&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="continuum--resurrect-issues">continuum &amp; resurrect issues</h3>
<p>I had some issues getting tmux resurrect and continuum to work on my new machine. It turned out it was due to the ordering
of my plugins. Any plugin that edits the right status bar, like the catppuccin theme plugin needs to go before these two.
Now my tmux sessions automatically save and reload themselves. So when I shut down my PC I can carry on from where I left.</p>
<p>Like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="c1"># must be before continuum edits right status bar</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">plugin</span> <span class="o">=</span> <span class="n">tmuxPlugins</span><span class="o">.</span><span class="n">catppuccin</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">extraConfig</span> <span class="o">=</span> <span class="s1">&#39;&#39; 
</span></span></span><span class="line"><span class="cl"><span class="s1">    set -g @catppuccin_flavour &#39;frappe&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">    set -g @catppuccin_window_tabs_enabled on
</span></span></span><span class="line"><span class="cl"><span class="s1">    set -g @catppuccin_date_time &#34;%H:%M&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">    &#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">plugin</span> <span class="o">=</span> <span class="n">tmuxPlugins</span><span class="o">.</span><span class="n">resurrect</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">extraConfig</span> <span class="o">=</span> <span class="s1">&#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">    set -g @resurrect-strategy-vim &#39;session&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">    set -g @resurrect-strategy-nvim &#39;session&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">    set -g @resurrect-capture-pane-contents &#39;on&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">    &#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">plugin</span> <span class="o">=</span> <span class="n">tmuxPlugins</span><span class="o">.</span><span class="n">continuum</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">extraConfig</span> <span class="o">=</span> <span class="s1">&#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">    set -g @continuum-restore &#39;on&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">    set -g @continuum-boot &#39;on&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">    set -g @continuum-save-interval &#39;10&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">    &#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>I haven&rsquo;t worked out how to restore my nvim session, however. The resurrect plugin needs a session.vim file to do that.
For now, this is good enough, as I have a session manager with nvim itself so it is simply a button press to restore it.</p>
<p>If you are using home-manager make sure to import this module into your main home manager nix file such as <code>home.nix</code>.
Then you should be able to run <code>home-manager switch</code> to install the tmux, and its plugins and build the config at
<code>~/.config/tmux/tmux.conf</code>.</p>
<p><a href="https://gitlab.com/hmajid2301/dotfiles/-/blob/ccb08009df7e0d884db790bca57317748661e35b/home-manager/programs/tmux.nix">My tmux file</a></p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Why I moved to NixOS</title>
      <link>https://haseebmajid.dev/posts/2023-06-25-why-i-moved-to-nixos/</link>
      <pubDate>Sun, 25 Jun 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-06-25-why-i-moved-to-nixos/</guid>
      <description>&lt;p&gt;Recently I decided to move from Arch Linux to NixOS. Rather than doing what I should&amp;rsquo;ve done which was
try to use NixOS in a VM first and learn Nix properly. I decided to jump into the deep end and completely delete my
current Arch Linux build with Nix. This included replacing my dotfiles setup using DotBot moving them to Nix flakes and
home-manager (more on this in a bit). In this post, I&amp;rsquo;ll go over why I moved over to NixOS, and why I am staying put for now.
What I like about it and some difficulties I have had.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Recently I decided to move from Arch Linux to NixOS. Rather than doing what I should&rsquo;ve done which was
try to use NixOS in a VM first and learn Nix properly. I decided to jump into the deep end and completely delete my
current Arch Linux build with Nix. This included replacing my dotfiles setup using DotBot moving them to Nix flakes and
home-manager (more on this in a bit). In this post, I&rsquo;ll go over why I moved over to NixOS, and why I am staying put for now.
What I like about it and some difficulties I have had.</p>
<p>Also, NixOS is the new Arch meme.</p>
<p></p>
<details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">New to NixOS</summary>
  
  I&rsquo;m pretty new to NixOS so there is a chance something I have said isn&rsquo;t quite correct.
Please let me know via Gitlab Issue (click on the edit button).
</details>

<h2 id="background">Background</h2>
<p>I first heard about NixOS last year at EuroPython 2022, when I went to a round table discussion around tooling. This was
mostly focused on CLI tools that people used. But someone mentioned they used NixOS and you could have your entire machines config
in a single repository. I was intrigued. I personally love reproducibility, and I have enjoyed using Docker for that very reason.
Though I have since learnt Docker is repeatable but Nix with Flakes is far more reproducible. Anyways tangent aside, I want and explored
but I just couldn&rsquo;t be bothered to swap. So I stuck with Arch Linux the main reason being it seemed like Arch with its AUR (arch user repository)
had far more packages.</p>
<p>Then recently I have been looking to improve my developer workflow, I have moved mostly to neovim with tmux (see previous posts in the series).
I was using pop shell as a &ldquo;pseudo&rdquo; tiling manager, and it was fine, to begin with. Then I started to explore more and came across Hyprland,
a Wayland compositor. Which looked quite powerful, and more flexible than what I had. For example, I had it set up so that workspaces could only
change my primary display. AFAIK on Gnome you couldn&rsquo;t have workspaces per monitor, changing workspace would change it on both monitors.</p>
<p>However, with Hyprland (and many other such window managers etc) you can set a workspace per monitor. I had tried to use qtile before but soon
found myself not wanting to configure everything. But I am much more experienced with Linux and said why not.
So essentially my initial reasons for moving were I wanted to have my machine configured via a single git repo and have it reproducible and also
try a new window manager, so I thought let us do both at once :laugh:</p>
<p>Around this time to make it even more interesting I decided to try and see if I could live just working on my Framework laptop which I barely used since
I bought it. In theory, moving back to my desktop should be a lot easier if I use NixOS.</p>
<h2 id="nix-vs-nixos">Nix vs NixOS</h2>
<p>The language can be a bit confusing so let&rsquo;s define what we mean: <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<ul>
<li>nix: The package manager, think like apt or Pacman</li>
<li>nixlang: The nix programming language, a functional language</li>
<li>nixos: An operating system built around nix package manager</li>
<li>machine/system/laptop/desktop: I use these words interchangeably, for NixOS I&rsquo;ve only set it up on my Laptop</li>
</ul>
<p>You can run Nix in any Linux distribution (and even macOS), so what I probably should have done is started using the Nix package manager first.
Then tried NixOS in a virtual machine (on my Arch machine). Then after I was comfortable with nix and nixlang, assuming I liked it, move properly
NixOS on my system.</p>
<h2 id="moving-to-nixos">Moving to NixOS</h2>
<p></p>
<p>So I moved to NixOS using their graphical installer, which was very easy to use and set up. Even setting up hibernate with ease, which I remember being
a pain on Arch Linux (mainly due to my lack of understanding but none the less nixos made it pretty easy).</p>
<p>Now Arch Linux is a rolling release distribution which means rather than getting &ldquo;big&rdquo; updates every 6 months with say POP!_OS (my previous distro).
You receive updates far more frequently, so you have a more up-to-date system. The down-side things can break, my most recent issue being
Plymouth breaking and me not being able to type in my password to decrypt my LUKs disk, so not being able to boot.
I then need to use a live USB to remove the Plymouth config from my grub setup.</p>
<p>So one of the features that convinced me to move to nixos was the ability to roll back to previous generations.
So if I install or update a package and it breaks my system I can roll back to the previous generation.
So the way my Arch system broke would not happen in NixOS alongside this and the ability to define the state of my system
in &ldquo;code&rdquo; was enough to convince me.</p>
<p></p>
<p>Another feature I liked was able to define my system state in code (nixlang). The idea being I could setup
on a new machine a lot quicker than I could with another Distro.</p>
<p>My dotfiles setup at the time used dotbot which did help me get set up faster but still involved lots of manual steps.
I thought about using Ansible to help configure my setup, but in the end, never got around to doing it.
So having the ability to define my system state in nixlang was a nice feature to have.</p>
<p>The final feature that I liked was the ability of atomic updates. So nix never overwrites packages it simply adds them to a new path
(in the /nix/store directory). It means we can maintain multiple versions of the same library (think like in Golang). It also means
there is no team where we have some of the old files and new ones. So if your upgrade fails in the middle, we can simply revert to the older version. <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
<h3 id="summary">Summary</h3>
<ul>
<li>Define system state in Nixlang</li>
<li>Roll back to previous generations</li>
<li>Atomic updates</li>
</ul>
<h2 id="learnings">Learnings</h2>
<p>In this section, I will go over a few of my learnings, since picking up NixOS a month ago. It is not a beginner-friendly distribution.
If you are new to Linux I recommend trying a distribution like POP!_OS first then after you are more comfortable with Linux you can try
nix as a package manager and after that move to NixOS. I will go over more why later in this post.</p>
<p>There was a bunch of confusion about how to set up my repository to configure my system. What I ended up with was the following <sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup></p>
<ul>
<li>Use Nix Flakes <sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup></li>
<li>Improves reproducibility</li>
<li>Use home-manager to configure dotfiles</li>
<li>Use NixOS configuration for the rest of the system
<ul>
<li>Such as bootloader</li>
<li>Luks encryption</li>
</ul>
</li>
<li>Allow multiple different host machines (laptop, desktop, etc)
<ul>
<li>Share common config</li>
</ul>
</li>
<li>Separate home-manager config
<ul>
<li>So can use machines that won&rsquo;t run NixOS (like work laptop)</li>
</ul>
</li>
</ul>
<h3 id="imperative-vs-declarative">Imperative vs Declarative</h3>
<p>I wanted my config to be declarative, which means I tell NixOS I want x,y,z i.e. Grub, Mullvad VPN and then NixOS works out how to get the system into the correct state.
Nix/NixOS have some imperative operations <sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> like using <code>nix-env</code> is which more similar to other package managers to install packages. But I avoid using those.
Except <code>nix-shell -p &lt;pkg&gt;</code>, which creates a shell with the package installed temporarily which is great for quickly trying out the package without committing to an install.
Essentially I want to define my system state in code, so it can be reproduced on other machines.</p>
<h3 id="nix-flakes">Nix Flakes</h3>
<p>Again I&rsquo;m pretty new to Nix but I think from what I&rsquo;ve seen Nix flakes are the recommended way to use Nix.
Flakes essentially provide us with a lot more reproducibility. They are self-contained units which have inputs and outputs.
A flake needs a <code>flake.nix</code> file, which contains inputs and outputs.
The inputs can be other flakes (like home-manager), and these get locked to specific versions in a <code>flake.lock</code> file.</p>
<p>Where out flake file may look like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">description</span> <span class="o">=</span> <span class="s2">&#34;My NixOS Config&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">inputs</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">nixpkgs</span><span class="o">.</span><span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;github:nixos/nixpkgs/nixos-unstable&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">home-manager</span><span class="o">.</span><span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;github:nix-community/home-manager&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">outputs</span> <span class="o">=</span> <span class="p">{</span> <span class="n">self</span><span class="o">,</span> <span class="n">nixpkgs</span><span class="o">,</span> <span class="n">home-manager</span><span class="o">,</span> <span class="n">nur</span><span class="o">,</span> <span class="o">...</span> <span class="p">}</span><span class="o">@</span><span class="n">inputs</span><span class="p">:</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>They also help with structuring nix repositories acting as a &ldquo;main&rdquo; function [4]. When I start doing programming projects
I will likely look into using flakes for even better reproducibility as compared with Docker.</p>
<h3 id="home-manager">Home Manager</h3>
<p>Home Manager makes it easier to manage the user environment using the Nix package manager (again declaratively). Anything related to the user
is managed here. Things like dotfiles, which applications to install and which config such as alacritty as my terminal.</p>
<p>In theory, my config is set up to support multiple users but at the moment there is only one user <code>haseeb</code>.
One nice feature I didn&rsquo;t consider was how it would be to swap back to Gnome from Hyprland. This makes it much easier to experiment
and try out new window managers or desktop environments. My current config has both my gnome config and hyprland config and with a few lines
I should be able to back to Gnome, if I want (and vice versa).</p>
<p>As you can probably imagine most of my config is here, as most changes happen in userland. I can run this separately to NixOS because
I will have machines where I cannot use NixOS, like my work laptop but can use the nix package manager.</p>
<p>command: <code>home-manager switch --flake ~/dotfiles#haseeb@framework</code></p>
<h3 id="nixos-configuration">NixOS Configuration</h3>
<p>This is like home manager except for system-wide changes, split up via device (currently only my framework laptop).
Things like the bootloader, and fingerprint reader. I only put config here if it needs to be system-wide.</p>
<p>This also builds the home manager config</p>
<p><a href="https://gitlab.com/hmajid2301/dotfiles/-/blob/6f2bac80e57999c793eb8ae48ca1dfc8dafa8f9e/hosts/framework/default.nix">Click here for an example</a>.
The common config can be shared</p>
<p>command: <code>sudo nixos-rebuild switch --flake ~/dotfiles#framework</code></p>
<h3 id="issues">Issues</h3>
<p>Some issues I encountered whilst moving to NixOS. Lots of docs still refer to nix-env and nix-channel as a way to install packages.
But from what I&rsquo;ve read and my personal preference it seems the preferred way is the declarative way I described above.
However, when you first get started this is not always clear</p>
<p>Docs are a bit messy in my opinion, I often find myself reading multiple different blog posts to understand what to do.
Or looking through <a href="https://sourcegraph.com">sourcegraph</a> for code examples and working it out that way.
As I said you will still see references to the imperative way to use nix.</p>
<p>I also think the error messages that home-manager and NixOS configuration can throw, can at times be a bit obtuse and hard to
understand (at least for me).</p>
<p>Overall these are just small issues, which I hope will get better over time. Nix/NixOS can be infuriating at times, when you know
on another distribution/package manager would be much quicker. However knowing that my builds are more reproducible is worth it!</p>
<p>That&rsquo;s it! Some reasons why I&rsquo;ve moved to NixOS and why I will be staying for now. In a future article, I will break down my own
NixOS configuration.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/dotfiles/-/tree/6f2bac80e57999c793eb8ae48ca1dfc8dafa8f9e">Nix Config</a></li>
<li><a href="https://thiscute.world/en/posts/nixos-and-flake-basics/#iii-nix-flakes-and-the-old-nix">NixOS Basic, a great read imo!</a></li>
</ul>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://news.ycombinator.com/item?id=23251895">https://news.ycombinator.com/item?id=23251895</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://nixos.org/guides/how-nix-works.html">https://nixos.org/guides/how-nix-works.html</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>Heavily inspired by this repo: <a href="https://github.com/Misterio77/nix-config">https://github.com/Misterio77/nix-config</a>&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p><a href="https://www.tweag.io/blog/2020-05-25-flakes/">https://www.tweag.io/blog/2020-05-25-flakes/</a>&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p><a href="https://nixos.wiki/wiki/Overview_of_the_NixOS_Linux_distribution">https://nixos.wiki/wiki/Overview_of_the_NixOS_Linux_distribution</a>&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to use NUR with Home-Manager &amp; Nix Flakes</title>
      <link>https://haseebmajid.dev/posts/2023-06-22-til-use-nur-with-home-manager-flake/</link>
      <pubDate>Thu, 22 Jun 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-06-22-til-use-nur-with-home-manager-flake/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to use NUR with Home-Manager &amp;amp; Nix Flakes&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;NUR is the Nix user repository like the Arch user repository (AUR). It exists so that:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The NUR was created to share new packages from the community in a faster and more decentralized way. - &lt;a href=&#34;https://github.com/nix-community/NUR&#34;&gt;https://github.com/nix-community/NUR&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If we want to install packages from NUR in our home manager config which is set up using Nix Flakes.
Assuming you build your home manager like&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to use NUR with Home-Manager &amp; Nix Flakes</strong></p>
<p>NUR is the Nix user repository like the Arch user repository (AUR). It exists so that:</p>
<blockquote>
<p>The NUR was created to share new packages from the community in a faster and more decentralized way. - <a href="https://github.com/nix-community/NUR">https://github.com/nix-community/NUR</a></p>
</blockquote>
<p>If we want to install packages from NUR in our home manager config which is set up using Nix Flakes.
Assuming you build your home manager like</p>
<p>To do so first add it as an input in our <code>flake.nix</code> file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl">  <span class="n">inputs</span> <span class="err">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">nixpkgs</span><span class="o">.</span><span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;github:nixos/nixpkgs/nixos-unstable&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">nur</span><span class="o">.</span><span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;github:nix-community/NUR&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span></code></pre></div><p>Then we can use with home-manager by importing <code>inputs.nur.hmModules.nur</code>, for example in your <code>home.nix</code> file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl">  <span class="n">imports</span> <span class="err">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="n">inputs</span><span class="o">.</span><span class="n">nur</span><span class="o">.</span><span class="n">hmModules</span><span class="o">.</span><span class="n">nur</span>
</span></span><span class="line"><span class="cl">  <span class="p">]</span>
</span></span></code></pre></div><p>Then we should be able to import packages like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl">  <span class="n">home</span><span class="o">.</span><span class="n">packages</span> <span class="err">=</span> <span class="k">with</span> <span class="n">pkgs</span><span class="p">;</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="n">nur</span><span class="o">.</span><span class="n">repos</span><span class="o">.</span><span class="n">peel</span><span class="o">.</span><span class="n">rofi-wifi-menu</span>
</span></span><span class="line"><span class="cl">    <span class="n">nur</span><span class="o">.</span><span class="n">repos</span><span class="o">.</span><span class="n">peel</span><span class="o">.</span><span class="n">rofi-emoji</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span></code></pre></div><blockquote>
<p>Note these packages won&rsquo;t work because they are out of date, and they caused me a lot of grief 😅, thinking it wasn&rsquo;t working.</p>
</blockquote>
<p>But that&rsquo;s it! You should be able to use NUR packages with home-manager setup via a flake.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Declaratively Setup Mullvad VPN with NixOS</title>
      <link>https://haseebmajid.dev/posts/2023-06-20-til-how-to-declaratively-setup-mullvad-with-nixos/</link>
      <pubDate>Tue, 20 Jun 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-06-20-til-how-to-declaratively-setup-mullvad-with-nixos/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Declaratively Setup Mullvad VPN with NixOS&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I have recently moved to NixOS, one of the great features of NixOS is that you can set up your entire machine
from a single git repo. We can do this declaratively, what we mean by this is we tell nix what we want the state to
be and nixos will work out how to get there.&lt;/p&gt;
&lt;p&gt;For example, we can install Mullvad set various options already. This means on a new machine we don&amp;rsquo;t manually have to
setup these mullvad settings.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Declaratively Setup Mullvad VPN with NixOS</strong></p>
<p>I have recently moved to NixOS, one of the great features of NixOS is that you can set up your entire machine
from a single git repo. We can do this declaratively, what we mean by this is we tell nix what we want the state to
be and nixos will work out how to get there.</p>
<p>For example, we can install Mullvad set various options already. This means on a new machine we don&rsquo;t manually have to
setup these mullvad settings.</p>
<p>We can set it up by doing something like <code>mullvad.nix</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span><span class="n">pkgs</span><span class="o">,</span> <span class="n">config</span><span class="o">,</span> <span class="o">...</span><span class="p">}:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">environment</span><span class="o">.</span><span class="n">systemPackages</span> <span class="o">=</span> <span class="p">[</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">mullvad-vpn</span> <span class="n">pkgs</span><span class="o">.</span><span class="n">mullvad</span> <span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="n">services</span><span class="o">.</span><span class="n">mullvad-vpn</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">systemd</span><span class="o">.</span><span class="n">services</span><span class="o">.</span><span class="s2">&#34;mullvad-daemon&#34;</span><span class="o">.</span><span class="n">postStart</span> <span class="o">=</span> <span class="k">let</span>
</span></span><span class="line"><span class="cl">    <span class="n">mullvad</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">services</span><span class="o">.</span><span class="n">mullvad-vpn</span><span class="o">.</span><span class="n">package</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">in</span> <span class="s1">&#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">    while ! </span><span class="si">${</span><span class="n">mullvad</span><span class="si">}</span><span class="s1">/bin/mullvad status &gt;/dev/null; do sleep 1; done
</span></span></span><span class="line"><span class="cl"><span class="s1">    </span><span class="si">${</span><span class="n">mullvad</span><span class="si">}</span><span class="s1">/bin/mullvad auto-connect set on
</span></span></span><span class="line"><span class="cl"><span class="s1">    </span><span class="si">${</span><span class="n">mullvad</span><span class="si">}</span><span class="s1">/bin/mullvad tunnel ipv6 set on
</span></span></span><span class="line"><span class="cl"><span class="s1">    </span><span class="si">${</span><span class="n">mullvad</span><span class="si">}</span><span class="s1">/bin/mullvad set default \
</span></span></span><span class="line"><span class="cl"><span class="s1">        --block-ads --block-trackers --block-malware
</span></span></span><span class="line"><span class="cl"><span class="s1">  &#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The main part here is we set up mullvad and then in the service we set various options. That can be set via the
GUI app.</p>
<pre tabindex="0"><code>${mullvad}/bin/mullvad set default \
        --block-ads --block-trackers --block-malware
</code></pre><p>One improvement we could make is to use a secret manager like <code>nix-sops</code> and even login automatically using our account id [1].</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/dotfiles/-/blob/6f2bac80e57999c793eb8ae48ca1dfc8dafa8f9e/hosts/common/optional/mullvad.nix">Dotfiles</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Add Vim Navigation FZF</title>
      <link>https://haseebmajid.dev/posts/2023-05-30-til-how-to-vim-navigation-to-fzf/</link>
      <pubDate>Tue, 30 May 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-05-30-til-how-to-vim-navigation-to-fzf/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Add Vim Navigation FZF&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/junegunn/fzf&#34;&gt;FZF&lt;/a&gt; is an amazing fuzzy finder tool, that is super flexible and you can create some
cool cli one-liners with it. For example, I use it with the fish shell plugin to search through my command history (CTRL + R).&lt;/p&gt;
&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2023-05-30-til-how-to-vim-navigation-to-fzf/images/fzf.jpg&#34;
        type=&#34;&#34;
        alt=&#34;FZF Example&#34;
        
      /&gt;&lt;/p&gt;
&lt;p&gt;In this post, I will show you how vim style navigation to the FZF pop-up preview. All we need to do is add the following to the
relevant file i.e. &lt;code&gt;fish.config&lt;/code&gt;, &lt;code&gt;.bashrc&lt;/code&gt; or &lt;code&gt;.zshrc&lt;/code&gt;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Add Vim Navigation FZF</strong></p>
<p><a href="https://github.com/junegunn/fzf">FZF</a> is an amazing fuzzy finder tool, that is super flexible and you can create some
cool cli one-liners with it. For example, I use it with the fish shell plugin to search through my command history (CTRL + R).</p>
<p><img
        loading="lazy"
        src="/posts/2023-05-30-til-how-to-vim-navigation-to-fzf/images/fzf.jpg"
        type=""
        alt="FZF Example"
        
      /></p>
<p>In this post, I will show you how vim style navigation to the FZF pop-up preview. All we need to do is add the following to the
relevant file i.e. <code>fish.config</code>, <code>.bashrc</code> or <code>.zshrc</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">FZF_DEFAULT_OPTS</span><span class="o">=</span><span class="s2">&#34;--bind &#39;j:down,k:up,ctrl-j:preview-down,ctrl-k:preview-up&#39;&#34;</span>
</span></span></code></pre></div><p>This will mean we can move and up down using the normal Vim binding <code>j</code> and <code>k</code></p>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to change the fish greeter</title>
      <link>https://haseebmajid.dev/posts/2023-05-25-til-how-to-change-fish-greeter/</link>
      <pubDate>Thu, 25 May 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-05-25-til-how-to-change-fish-greeter/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to change the fish greeter&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In this post I will show you how you can change your fish shell greeter from the default&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Welcome to fish, the friendly interactive shell
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Type &lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;help&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; instructions on how to use fish
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To something custom, this will run every time you open a new shell!&lt;/p&gt;
&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2023-05-25-til-how-to-change-fish-greeter/images/greeting.png&#34;
        type=&#34;&#34;
        alt=&#34;Fish Greeting&#34;
        
      /&gt;&lt;/p&gt;
&lt;p&gt;To do this, go to your &lt;code&gt;fish_greeter.fish&lt;/code&gt; file which can usually be found at &lt;code&gt;~/.config/fish/functions/fish_greeting.fish&lt;/code&gt;.
Where mine looks like:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to change the fish greeter</strong></p>
<p>In this post I will show you how you can change your fish shell greeter from the default</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">Welcome to fish, the friendly interactive shell
</span></span><span class="line"><span class="cl">Type <span class="sb">`</span><span class="nb">help</span><span class="sb">`</span> <span class="k">for</span> instructions on how to use fish
</span></span></code></pre></div><p>To something custom, this will run every time you open a new shell!</p>
<p><img
        loading="lazy"
        src="/posts/2023-05-25-til-how-to-change-fish-greeter/images/greeting.png"
        type=""
        alt="Fish Greeting"
        
      /></p>
<p>To do this, go to your <code>fish_greeter.fish</code> file which can usually be found at <code>~/.config/fish/functions/fish_greeting.fish</code>.
Where mine looks like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fish" data-lang="fish"><span class="line"><span class="cl"><span class="k">function</span> <span class="nb">fish_greeting
</span></span></span><span class="line"><span class="cl"><span class="nb">    </span><span class="nf">fortune</span> <span class="o">|</span> <span class="nf">lolcat</span> <span class="na">-f</span> <span class="o">|</span> <span class="nf">chara</span> say <span class="na">-c</span> kitten
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span></code></pre></div><p>fortune generates a random quote, then lolcat colours that quote as a rainbow and finally chara prints out the kitten.</p>
<h2 id="nix-optional">Nix (Optional)</h2>
<p>If you are using fish to configure your fish shell you can do something like (I do this in a file called <code>fish.nix</code>):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nix" data-lang="nix"><span class="line"><span class="cl"><span class="p">{</span> <span class="n">lib</span><span class="o">,</span> <span class="n">pkgs</span><span class="o">,</span> <span class="o">...</span> <span class="p">}:</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">programs</span><span class="o">.</span><span class="n">fish</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">enable</span> <span class="o">=</span> <span class="no">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">functions</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">fish_greeting</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">description</span> <span class="o">=</span> <span class="s2">&#34;Greeting to show when starting a fish shell&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">body</span> <span class="o">=</span> <span class="s2">&#34;fortune | lolcat -f | chara say -c kitten&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div>]]></content:encoded>
    </item>
    
    <item>
      <title>My Development Workflow With Linux</title>
      <link>https://haseebmajid.dev/posts/2023-05-08-my-development-workflow-with-linux/</link>
      <pubDate>Mon, 08 May 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-05-08-my-development-workflow-with-linux/</guid>
      <description>&lt;p&gt;Following on from my previous post about dotfiles and my development workflow (WIP), in this post I will go over my
operating system.&lt;/p&gt;
&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2023-05-08-my-development-workflow-with-linux/images/dev.png&#34;
        type=&#34;&#34;
        alt=&#34;Development Window&#34;
        
      /&gt;
&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2023-05-08-my-development-workflow-with-linux/images/fun.png&#34;
        type=&#34;&#34;
        alt=&#34;Fun Window&#34;
        
      /&gt;&lt;/p&gt;
&lt;h2 id=&#34;arch&#34;&gt;Arch&lt;/h2&gt;
&lt;p&gt;As the name implies I use Linux as my main operating system. I am a big fan of Linux because not only is free and
open-source, it provides a great developer experience where I find tools that just work on Linux. It also is a lot more
customisable as compared with Windows or MacOS. Finally, I tend to find it more lightweight as well and takes fewer
resources to run especially if you are using Docker.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Following on from my previous post about dotfiles and my development workflow (WIP), in this post I will go over my
operating system.</p>
<p><img
        loading="lazy"
        src="/posts/2023-05-08-my-development-workflow-with-linux/images/dev.png"
        type=""
        alt="Development Window"
        
      />
<img
        loading="lazy"
        src="/posts/2023-05-08-my-development-workflow-with-linux/images/fun.png"
        type=""
        alt="Fun Window"
        
      /></p>
<h2 id="arch">Arch</h2>
<p>As the name implies I use Linux as my main operating system. I am a big fan of Linux because not only is free and
open-source, it provides a great developer experience where I find tools that just work on Linux. It also is a lot more
customisable as compared with Windows or MacOS. Finally, I tend to find it more lightweight as well and takes fewer
resources to run especially if you are using Docker.</p>
<p><a href="https://github.com/overmighty/i-use-arch-btw"><em>Insert meme about how I use arch btw</em></a>.
More seriously the main reason I use Arch is because of access to the [Aur]((<a href="https://aur.archlinux.org/)">https://aur.archlinux.org/)</a>, the Arch user
repository.</p>
<p>The AUR provides an easy way for us to install any package using a package manager like <code>yay</code> or <code>pamac</code>.
It is similar to <code>brew</code> that people use on MacOS. If I want to install a package I can usually do something like</p>
<p><code>yay -S &lt;package&gt;</code> some of the recent things I have installed can be seen below</p>
<p><img
        loading="lazy"
        src="/posts/2023-05-08-my-development-workflow-with-linux/images/aur.png"
        type=""
        alt="AUR Installs"
        
      /></p>
<p>So if I didn&rsquo;t use the AUR I would&rsquo;ve had to say git clone the project and then run a command. It also then means updating
is a pain as I need to do it manually by remembering to do it. Rather than leaving it to a package manager to tell us
when to update.</p>
<h2 id="gnome">Gnome</h2>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden">
  <iframe src="https://yewtu.be/embed/AVv4q84FGpw"
    style="position:absolute;top:0;left:0;width:100%;height:100%;border:0" allowfullscreen></iframe>
</div>

<p><a href="https://gitlab.com/hmajid2301/dotfiles/-/blob/8da9d515234d050dc34703e94f647fd9cb40c61a/linux/gnome/settings.ini">Link to config</a></p>
<p>I use gnome as my desktop environment (with Wayland), I tried using Qtile and AwesomeWM but found I didn&rsquo;t enjoy tinkering
with things at that low of a level and prefer just having a lot of the basics set up for me. For example, even something as
simple as the volume indicator I could never style it to how I wanted it to look. So I eventually came back to Gnome
and I have been able to customise it enough to my liking.</p>
<h3 id="shortcuts">Shortcuts</h3>
<p>Some shortcuts I use:</p>
<ul>
<li><code>super + t</code>: Opens an alacritty terminal</li>
<li><code>super + a</code>: Opens search light for opening apps similar to spotlight search in MacOS</li>
</ul>
<h3 id="extensions">Extensions</h3>
<p>The main way I customise gnome is by using extensions</p>
<p><a href="https://github.com/pop-os/shell">Pop Shell</a> is used for tiling I tried a tiling window manager in the past and to be honest
this extension is good enough for me whilst not being as powerful as say Qtile or AwesomeWM. For my use case, it is
absolutely fine.</p>
<p>I can navigate windows just using my keyboard <code>super + h j k l</code> to move between windows similar to how we would in vim.
It also has some shortcuts to maximise a window so it can ignore the tiling when needed. But again the main thing is
I don&rsquo;t need to use my mouse I can use my keyboard.</p>
<p><a href="https://extensions.gnome.org/extension/5338/aylurs-widgets/">Aylurs Widgets</a> You can think of this as a collection of
extensions which provides features such as making the top bar look nicer and the drop-down settings menu.</p>
<p><a href="https://extensions.gnome.org/extension/615/appindicator-support/">AppIndicator</a> shows app indicators in the top bar
for different applications like Discord or mullvad vpn.</p>
<p><a href="https://extensions.gnome.org/extension/1010/archlinux-updates-indicator/">Updates Indicator</a> lets me know via an app
indicator that updates are available then when we click the button it will open a terminal and run <code>yay</code> to update.</p>
<p><a href="https://extensions.gnome.org/extension/5090/space-bar/">Space Bar</a>: I replace Aylurs workspace with this extension.
It is a bit more powerful and I think it looks better.</p>
<p><a href="https://extensions.gnome.org/extension/5489/search-light/">Search Light</a>: Like spotlight search for MacOS.</p>
<p>Some other extensions I use include:</p>
<ul>
<li><a href="https://extensions.gnome.org/extension/5237/rounded-window-corners/">Rounded Window Corners</a>: Purely for
aethestic purposes</li>
<li><a href="https://extensions.gnome.org/extension/5279/pano/">Pano</a>: A better clipboard manager</li>
<li><a href="https://extensions.gnome.org/extension/3843/just-perfection/">Just Perfection</a>: Allows you to tweak almost everything in gnome</li>
<li><a href="https://extensions.gnome.org/extension/3193/blur-my-shell/">Blur my Shell</a>: Blur shell when viewing context menu</li>
<li><a href="https://extensions.gnome.org/extension/4451/logo-menu/">Logo Menu</a>: Adds a logo to top bar, similar to MacOS.</li>
</ul>
<h2 id="cli-tools">CLI Tools</h2>
<p>I have added all of the <a href="https://github.com/ibraheemdev/modern-unix">modern unix cli tools</a>.
Some CLI tools I use daily include:</p>
<ul>
<li><a href="https://github.com/junegunn/fzf">fzf</a>: Fuzzy search tool, especially for reverse search in my terminal with <a href="https://github.com/PatrickF1/fzf.fish">fish shell</a></li>
<li><a href="https://github.com/ajeetdsouza/zoxide">zoxide</a>: Smarter cd tool, integrated well with fzf, nvim and tmux</li>
<li><a href="https://github.com/ogham/exa">exa</a>: A replacement for <code>ls</code> with better syntax highlighting</li>
<li><a href="https://github.com/BurntSushi/ripgrep">ripgrep</a>: A faster <code>grep</code></li>
<li><a href="https://github.com/denisidoro/navi">navi</a>: Interactive cheat sheet</li>
<li><a href="https://restic.net/">restic</a>: Backup tool used to backup my home directory to the cloud (off-site).</li>
</ul>
<h2 id="misc">Misc</h2>
<p>Some other &ldquo;fun&rdquo; things I have set up in Linux.</p>
<h3 id="wallpaper">Wallpaper</h3>
<p>I found some nice <a href="https://old.reddit.com/r/wallpapers/comments/3ueq55/lakeside_day_night_transition_credit_louis_coyle/">wallpaper</a>
which can change throughout the day depending on the sun. I use this with a script called <a href="https://github.com/hexive/sunpaper">sunpaper</a>.
This script lets us change the wallpaper depending on the time of day and the very cool it does is it uses the sun.
So it changes depending on where the sun is. For example, 15 minutes before the sunset it will change the wallpaper
and then after the sunset it will change it to another wallpaper you just need to set up a directory will wallpapers
named <code>1.jpg</code>, <code>2.jpg</code> &hellip;</p>
<p><img
        loading="lazy"
        src="/posts/2023-05-08-my-development-workflow-with-linux/images/wallpaper.gif"
        type=""
        alt="Wallpaper"
        
      /></p>
<p>Where my directory looks like (ignore the png files you don&rsquo;t need those):</p>
<p><img
        loading="lazy"
        src="/posts/2023-05-08-my-development-workflow-with-linux/images/wallpaper-directory.png"
        type=""
        alt="Wallpaper Directory"
        
      /></p>
<p>You can find my copy of the <a href="https://gitlab.com/hmajid2301/dotfiles/-/tree/8da9d515234d050dc34703e94f647fd9cb40c61a/wallpaper">script here</a>.</p>
<p>That&rsquo;s it! That&rsquo;s a quick blog post on my current Linux set-up.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/dotfiles">My Dotfiles</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>My Dotfiles II</title>
      <link>https://haseebmajid.dev/posts/2023-05-04-my-dotfiles-ii/</link>
      <pubDate>Thu, 04 May 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-05-04-my-dotfiles-ii/</guid>
      <description>&lt;p&gt;I know I recently made a post about my dotfiles but I&amp;rsquo;ve made a few changes since then, so here are my updated dotfiles.&lt;/p&gt;
&lt;details
  class=&#34;notice caution&#34;
  open=&#34;true&#34;
&gt;
    &lt;summary class=&#34;notice-title&#34;&gt;out of date&lt;/summary&gt;
  
  These dotfiles are out of date check out my updated ones &lt;a href=&#34;https://haseebmajid.dev/posts/2023-07-15-my-dotfiles-iii/&#34;&gt;here&lt;/a&gt;
&lt;/details&gt;

&lt;h2 id=&#34;system-overview&#34;&gt;System Overview&lt;/h2&gt;
&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2023-05-04-my-dotfiles-ii/images/dev.png&#34;
        type=&#34;&#34;
        alt=&#34;neovim&#34;
        
      /&gt;
&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2023-05-04-my-dotfiles-ii/images/fun.png&#34;
        type=&#34;&#34;
        alt=&#34;Tmux&#34;
        
      /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;OS: Arch Linux&lt;/li&gt;
&lt;li&gt;DE: Gnome&lt;/li&gt;
&lt;li&gt;Shell: Fish
&lt;ul&gt;
&lt;li&gt;Prompt: &lt;a href=&#34;https://starship.rs/&#34;&gt;Starship&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Terminal: Alacritty
&lt;ul&gt;
&lt;li&gt;Editor: Neovim (using &lt;a href=&#34;https://www.lazyvim.org&#34;&gt;LazyVim&lt;/a&gt; config)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Colorscheme: &lt;a href=&#34;https://github.com/catppuccin&#34;&gt;Catppuccin for EVERYTHING!!!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Fonts: &lt;a href=&#34;https://www.monolisa.dev/&#34;&gt;Mono Lisa&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;extensions&#34;&gt;Extensions&lt;/h3&gt;
&lt;p&gt;I use the following Gnome extension. You can find the config for the extensions in this &lt;a href=&#34;gnome/settings.ini&#34;&gt;massive file here&lt;/a&gt;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I know I recently made a post about my dotfiles but I&rsquo;ve made a few changes since then, so here are my updated dotfiles.</p>
<details
  class="notice caution"
  open="true"
>
    <summary class="notice-title">out of date</summary>
  
  These dotfiles are out of date check out my updated ones <a href="/posts/2023-07-15-my-dotfiles-iii/">here</a>
</details>

<h2 id="system-overview">System Overview</h2>
<p><img
        loading="lazy"
        src="/posts/2023-05-04-my-dotfiles-ii/images/dev.png"
        type=""
        alt="neovim"
        
      />
<img
        loading="lazy"
        src="/posts/2023-05-04-my-dotfiles-ii/images/fun.png"
        type=""
        alt="Tmux"
        
      /></p>
<ul>
<li>OS: Arch Linux</li>
<li>DE: Gnome</li>
<li>Shell: Fish
<ul>
<li>Prompt: <a href="https://starship.rs/">Starship</a></li>
</ul>
</li>
<li>Terminal: Alacritty
<ul>
<li>Editor: Neovim (using <a href="https://www.lazyvim.org">LazyVim</a> config)</li>
</ul>
</li>
<li>Colorscheme: <a href="https://github.com/catppuccin">Catppuccin for EVERYTHING!!!</a></li>
<li>Fonts: <a href="https://www.monolisa.dev/">Mono Lisa</a></li>
</ul>
<h3 id="extensions">Extensions</h3>
<p>I use the following Gnome extension. You can find the config for the extensions in this <a href="gnome/settings.ini">massive file here</a>.</p>
<ul>
<li><a href="https://github.com/pop-os/shell">Pop Shell</a></li>
<li><a href="https://extensions.gnome.org/extension/5090/space-bar/">Space Bar</a></li>
<li><a href="https://extensions.gnome.org/extension/5338/aylurs-widgets/">Aylurs Widgets</a></li>
<li><a href="https://extensions.gnome.org/extension/615/appindicator-support/">AppIndicator</a></li>
<li><a href="https://extensions.gnome.org/extension/1010/archlinux-updates-indicator/">Updates Indicator</a></li>
<li><a href="https://extensions.gnome.org/extension/3193/blur-my-shell/">Blur my Shell</a></li>
<li><a href="https://extensions.gnome.org/extension/5237/rounded-window-corners/">Rounded Window Corners</a></li>
<li><a href="https://extensions.gnome.org/extension/5279/pano/">Pano</a></li>
<li><a href="https://extensions.gnome.org/extension/3843/just-perfection/">Just Perfection</a></li>
<li><a href="https://extensions.gnome.org/extension/4451/logo-menu/">Logo Menu</a></li>
</ul>
<h3 id="top-bar">Top Bar</h3>
<p><img
        loading="lazy"
        src="/posts/2023-05-04-my-dotfiles-ii/images/topbar.png"
        type=""
        alt="Top Bar"
        
      /></p>
<ul>
<li><a href="https://gitlab.com/hmajid2301/dotfiles/-/blob/93133f7e829409a4a4c943ef38f22ffe2f5c3508/gnome/settings.ini#L763-942">Aylurs extension config</a></li>
<li>Rest of the top bar is configured using css <a href="themes/my_theme/gnome-shell/gnome-shell.css">here</a></li>
</ul>
<h3 id="applications">Applications</h3>
<p>I basically just installed every package from <a href="https://github.com/ibraheemdev/modern-unix">Modern Unix</a>.
You can find a full list of all the packages I &ldquo;use&rdquo; <a href="https://gitlab.com/hmajid2301/dotfiles/-/blob/main/meta/configs/packages.arch.yaml#L2-48">here</a>.</p>
<p>CLI tools that I use often include:</p>
<ul>
<li><a href="https://github.com/junegunn/fzf">fzf</a>: Fuzzy search tool
<ul>
<li>Especially for reverse search in my terminal with <a href="https://github.com/PatrickF1/fzf.fish">fish shell</a></li>
</ul>
</li>
<li><a href="https://github.com/ajeetdsouza/zoxide">zoxide</a>: Smarter cd tool, integrated well with fzf, nvim and tmux</li>
<li><a href="https://github.com/ogham/exa">exa</a>: A replacement for <code>ls</code> with better syntax highlighting</li>
<li><a href="https://github.com/BurntSushi/ripgrep">ripgrep</a>: A faster <code>grep</code></li>
<li><a href="https://github.com/denisidoro/navi">navi</a>: Interactive cheat sheet</li>
</ul>
<h3 id="wallpaper">Wallpaper</h3>
<p><img
        loading="lazy"
        src="/posts/2023-05-04-my-dotfiles-ii/images/wallpaper.gif"
        type=""
        alt="Wallpaper GIF"
        
      /></p>
<p>I wanted to have wallpaper that changes with the day, I slightly changed the <a href="https://github.com/hexive/sunpaper">sunpaper script</a>.
Which is a great script because it changes depending on the time of day i.e. when sunsets/rises.</p>
<h2 id="neovim">Neovim</h2>
<h3 id="screenshots">Screenshots</h3>
<p><img
        loading="lazy"
        src="/posts/2023-05-04-my-dotfiles-ii/images/neovim.png"
        type=""
        alt="Neovim"
        
      />
<img
        loading="lazy"
        src="/posts/2023-05-04-my-dotfiles-ii/images/neovim_telescope.png"
        type=""
        alt="Neovim Telescope"
        
      />
<img
        loading="lazy"
        src="/posts/2023-05-04-my-dotfiles-ii/images/neovim_noice.png"
        type=""
        alt="Neovim Noice"
        
      /></p>
<p>I have started using nvim as my default editor (IDE?). It uses <a href="lazyvim.org/">LazyVim</a> as
the base config and adds a few plugins on top.</p>
<ul>
<li><a href="https://github.com/folke/trouble.nvim">trouble</a>: For showing all lsp diagnostic issues in a file</li>
<li><a href="https://github.com/jvgrootveld/telescope-zoxide">telescope zoxide</a>: For showing all lsp diagnostic issues in a file</li>
</ul>
<p>Some of the nvim setup was heavily inspired by this <a href="https://github.com/colevoss/neovoss">repo</a>.
Including the status bar.</p>
<p>Check out this cool place for tracking my neovim setup:</p>
<ul>
<li><a href="https://dotfyle.com/hmajid2301/starter">https://dotfyle.com/hmajid2301/starter</a></li>
</ul>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://www.dropbox.com/sh/rqs2zce3ugf1dz2/AABam3J8BF5WOCvmYjVSXWKIa?dl=0">Dropbox with extra assets</a></li>
<li><a href="https://www.flaticon.com/free-icons/dot" title="dot icons">Dot icons created by Roundicons - Flaticon</a></li>
<li><a href="https://old.reddit.com/r/wallpapers/comments/3ueq55/lakeside_day_night_transition_credit_louis_coyle/">Wallpaper</a></li>
</ul>
<h3 id="inspired-by">Inspired By</h3>
<ul>
<li><a href="https://github.com/lime-desu/dootsfile">https://github.com/lime-desu/dootsfile</a></li>
<li><a href="https://github.com/ghostx31/dotfiles/tree/37587b043f277ff5831ce5f1a3287fbaec1d9fe3">https://github.com/ghostx31/dotfiles/tree/37587b043f277ff5831ce5f1a3287fbaec1d9fe3</a></li>
<li><a href="https://github.com/Anant-mishra1729/Village-Linux-rice">https://github.com/Anant-mishra1729/Village-Linux-rice</a></li>
<li><a href="https://github.com/colevoss/dotfiles">https://github.com/colevoss/dotfiles</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>My Development Workflow With Alacritty Fish Tmux Nvim</title>
      <link>https://haseebmajid.dev/posts/2023-05-02-my-development-workflow-with-alacritty-fish-tmux-nvim/</link>
      <pubDate>Tue, 02 May 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-05-02-my-development-workflow-with-alacritty-fish-tmux-nvim/</guid>
      <description>&lt;details
  class=&#34;notice info&#34;
  open=&#34;true&#34;
&gt;
    &lt;summary class=&#34;notice-title&#34;&gt;Workflows Change&lt;/summary&gt;
  
  This post is accurate as of date of publish. But likely will go stale, if I update my workflows I will likely publish
another post.
&lt;/details&gt;

&lt;p&gt;In this blog post, I will go over my current development workflow using the above tools namely, fish shell tmux and neovim.
I&amp;rsquo;ll be using the &lt;a href=&#34;https://gitlab.com/hmajid2301/dotfiles/-/tree/8da9d515234d050dc34703e94f647fd9cb40c61a&#34;&gt;dotfiles&lt;/a&gt;
found here.&lt;/p&gt;
&lt;p&gt;I aim to move away from using my mouse as much as possible as it just slows me down when my hands
are away from my keyboard.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">Workflows Change</summary>
  
  This post is accurate as of date of publish. But likely will go stale, if I update my workflows I will likely publish
another post.
</details>

<p>In this blog post, I will go over my current development workflow using the above tools namely, fish shell tmux and neovim.
I&rsquo;ll be using the <a href="https://gitlab.com/hmajid2301/dotfiles/-/tree/8da9d515234d050dc34703e94f647fd9cb40c61a">dotfiles</a>
found here.</p>
<p>I aim to move away from using my mouse as much as possible as it just slows me down when my hands
are away from my keyboard.</p>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">Theme</summary>
  
  I am using the catppuccin theme with the frappe variant.
</details>

<h2 id="alacritty">Alacritty</h2>
<p><a href="https://gitlab.com/hmajid2301/dotfiles/-/tree/8da9d515234d050dc34703e94f647fd9cb40c61a/alacritty">Link to config</a></p>
<p>This section should be pretty quick, <a href="https://alacritty.org/">alacritty</a> is the terminal I use on both Linux and MacOS.
I use it because it will run on Linux and MacOS so I can have a similar terminal between all of my devices where
I do any development. It is also GPU rendered so it should have better performance as compared with
the default terminals available on your OS.</p>
<p>It was born out of frustration with using vim inside tmux being slow. I haven&rsquo;t noticed
any issues with speed or lag when using alacritty that&rsquo;s the terminal I&rsquo;ll stick with.</p>
<p>Some people like to use <a href="https://sw.kovidgoyal.net/kitty/">kitty</a>, but I&rsquo;ve had no reason to swap yet.
One thing you cannot do in alacritty is open tabs, you can open multiple terminals or use tmux.</p>
<h3 id="config">config</h3>
<p>My Alacritty config doesn&rsquo;t have much of interest, except this snippet
which allows me to copy to my clipboard my selected text in the terminal and also right-click paste.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">selection</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">save_to_clipboard</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">mouse_bindings</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- {<span class="w"> </span><span class="nt">mouse: Right, action</span><span class="p">:</span><span class="w"> </span><span class="l">Paste }</span><span class="w">
</span></span></span></code></pre></div><p>However, as I&rsquo;m trying to use my mouse less this config is a lot less relevant</p>
<h4 id="macos">MacOS</h4>
<p>Since I want similar keymaps between Linux and MacOS I&rsquo;ve swapped the ctrl and super keys on my Mac.
So it matches my Linux machine and I don&rsquo;t have to memorise/get confused between
two different key bindings.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">key_bindings</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- {<span class="w"> </span><span class="nt">key: C, mods: Command, chars</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;\x03&#34;</span><span class="w"> </span>}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- {<span class="w"> </span><span class="nt">key: D, mods: Command, chars</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;\x05&#34;</span><span class="w"> </span>}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- {<span class="w"> </span><span class="nt">key: R, mods: Command, chars</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;\x13&#34;</span><span class="w"> </span>}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- {<span class="w"> </span><span class="nt">key: W, mods: Command, chars</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;\x17&#34;</span><span class="w"> </span>}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- {<span class="w"> </span><span class="nt">key: A, mods: Command, chars</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;\x01&#34;</span><span class="w"> </span>}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- {<span class="w"> </span><span class="nt">key: H, mods: Command, chars</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;\x08&#34;</span><span class="w"> </span>}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- {<span class="w"> </span><span class="nt">key: J, mods: Command, chars</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;\x0A&#34;</span><span class="w"> </span>}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- {<span class="w"> </span><span class="nt">key: K, mods: Command, chars</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;\x0B&#34;</span><span class="w"> </span>}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- {<span class="w"> </span><span class="nt">key: L, mods: Command, chars</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;\x0C&#34;</span><span class="w"> </span>}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- {<span class="w"> </span><span class="nt">key: Back, mods: Super, chars</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;\x15&#34;</span><span class="w"> </span>}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- {<span class="w"> </span><span class="nt">key: Left, mods: Super, chars</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;\eb&#34;</span><span class="w"> </span>}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- {<span class="w"> </span><span class="nt">key: Right, mods: Super, chars</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;\ef&#34;</span><span class="w"> </span>}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- {<span class="w"> </span><span class="nt">key: Left, mods: Command, chars</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;\eOH&#34;</span><span class="nt">, mode</span><span class="p">:</span><span class="w"> </span><span class="l">AppCursor }</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- {<span class="w"> </span><span class="nt">key: Right, mods: Command, chars</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;\eOF&#34;</span><span class="nt">, mode</span><span class="p">:</span><span class="w"> </span><span class="l">AppCursor }</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- {<span class="w"> </span><span class="nt">key: J, mods: Alt, chars</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;\ej&#34;</span><span class="w"> </span>}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- {<span class="w"> </span><span class="nt">key: K, mods: Alt, chars</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;\ek&#34;</span><span class="w"> </span>}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- {<span class="w"> </span><span class="nt">key: H, mods: Alt, chars</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;\eh&#34;</span><span class="w"> </span>}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- {<span class="w"> </span><span class="nt">key: L, mods: Alt, chars</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;\el&#34;</span><span class="w"> </span>}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- {<span class="w"> </span><span class="nt">key: N, mods: Alt, chars</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;\en&#34;</span><span class="w"> </span>}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- {<span class="w"> </span><span class="nt">key: Minus, mods: Alt, chars</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;\e-&#34;</span><span class="w"> </span>}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- {<span class="w"> </span><span class="nt">key: Equals, mods: Alt, chars</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;\e=&#34;</span><span class="w"> </span>}<span class="w">
</span></span></span></code></pre></div><p>So here <code>ctrl + c</code> (remember my super and ctrl keys are swapped) will map to <code>super + c</code> in the terminal to kill a process.</p>
<h2 id="fish">fish</h2>
<p><a href="https://gitlab.com/hmajid2301/dotfiles/-/tree/8da9d515234d050dc34703e94f647fd9cb40c61a/fish">Link to config</a></p>
<p>I use <a href="https://fishshell.com/">fish</a> shell as my default shell. You can replicate a lot of the features I am about to
discuss in zsh as well, using extensions. However, I found it slowed down zsh a lot and it wasn&rsquo;t as responsive as fish.</p>
<p>You should know that fish shell is not POSIX compliant, which means you cannot always run bash commands in fish the
syntax sometimes will vary.</p>
<p>Some features I like in fish include:</p>
<p>Suggested commands as you type it will try to guess which command to run. The last one in your history that best matches.</p>
<p><a href="images/suggestion.png">Auto Suggestions</a></p>
<p>I&rsquo;m also a big fan of using abbreviations <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> which are similar to aliases except when you the &ldquo;alias&rdquo; it gets converted
to the actual command you want to run. Say we have:</p>
<p><code>__git.create_abbr gco        git checkout</code></p>
<p>When we type <code>gco</code> the shell will expand into <code>git checkout</code>. This has a few advantages now in your
shell history we have <code>git checkout</code> not <code>gco</code> making it easier to search through. Also, we know which command we will
type.</p>
<p>Fish also provides some syntax highlighting for our commands which makes them nicer to look at because colours are great!</p>
<h3 id="plugins">Plugins</h3>
<p>Some fish plugins I use include:</p>
<p><a href="https://github.com/PatrickF1/fzf.fish">PatrickF1/fzf</a> integrates fzf (powerful fuzzy finder) nicely with fish.
My main use case is improving reverse search seen below.</p>
<p><img
        loading="lazy"
        src="/posts/2023-05-02-my-development-workflow-with-alacritty-fish-tmux-nvim/images/reverse-search.png"
        type=""
        alt="Reverse Search"
        
      /></p>
<p>But it can allow you to do lots of other things such as searching for files <code>ctrl + alt + f</code>.</p>
<p><img
        loading="lazy"
        src="/posts/2023-05-02-my-development-workflow-with-alacritty-fish-tmux-nvim/images/file-search.png"
        type=""
        alt="File Search"
        
      /></p>
<p><a href="https://github.com/edc/bass">edc/bass</a> allows us to run bash utilities in fish shell.</p>
<h3 id="starship">Starship</h3>
<p>As my shell prompt, I use <a href="https://starship.rs/">starship prompt</a> a super customisable font with great defaults.</p>
<p><img
        loading="lazy"
        src="/posts/2023-05-02-my-development-workflow-with-alacritty-fish-tmux-nvim/images/prompt.png"
        type=""
        alt="Starship Prompt"
        
      /></p>
<p>But overall I could probably easily swap to zsh but I find fish to be more responsive and feel snappier. It has some
nice features which make our lives a bit easier.</p>
<h2 id="tmux">tmux</h2>
<p><a href="https://gitlab.com/hmajid2301/dotfiles/-/tree/8da9d515234d050dc34703e94f647fd9cb40c61a/tmux">Link to config</a></p>
<p>I mentioned <a href="https://github.com/tmux/tmux">tmux</a> above. What is tmux? Well, it is in the name itself.
It&rsquo;s a terminal multiplexer, what does that mean? Well, it means we can open multiple
panes (think of each as its separate terminal).</p>
<p>One nice feature of tmux is that say your terminal crashes and the tmux session will still be alive.
We can simply reattach to it later.</p>
<p><a href="images/tmux.png">Tmux Panes</a></p>
<p>In the image above we have 3 panes.</p>
<h3 id="session-vs-window-vs-pane">Session vs Window vs Pane</h3>
<p>tmux has this concept of sessions, windows and panes</p>
<p><img
        loading="lazy"
        src="/posts/2023-05-02-my-development-workflow-with-alacritty-fish-tmux-nvim/images/tmux-server.png"
        type=""
        alt="Tmux Architecture"
        
      /> <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
<ul>
<li>a session can have multiples windows (think of a window as tabs in a terminal)</li>
<li>a window can have multiple panes</li>
<li>a pane is where we run our commands</li>
</ul>
<p>Typically I create a new session for each project I am working on.
For example, at the moment I have the following sessions:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># tmux ls</span>
</span></span><span class="line"><span class="cl">blog: <span class="m">2</span> windows <span class="o">(</span>created Sun Apr <span class="m">30</span> 22:37:51 2023<span class="o">)</span> <span class="o">(</span>attached<span class="o">)</span>
</span></span><span class="line"><span class="cl">bookmarkey: <span class="m">2</span> windows <span class="o">(</span>created Sun Apr <span class="m">30</span> 20:15:51 2023<span class="o">)</span>
</span></span><span class="line"><span class="cl">dotfiles: <span class="m">1</span> windows <span class="o">(</span>created Sun Apr <span class="m">30</span> 20:15:51 2023<span class="o">)</span>
</span></span><span class="line"><span class="cl">talks: <span class="m">1</span> windows <span class="o">(</span>created Sun Apr <span class="m">30</span> 20:15:51 2023<span class="o">)</span>
</span></span></code></pre></div><p>Where bookmarkey, for example, has two windows one for the gui and one for the api, as it is a full-stack application.</p>
<h3 id="smart-session-manager">Smart Session Manager</h3>
<p>To help manage my sessions I use <a href="https://github.com/joshmedeski/t-smart-tmux-session-manager">tmux smart session manager</a>.
This amazing plugin allows us to create a new session in a specific folder.</p>
<p>Outside of tmux, I can use <code>t</code> to open this menu below:</p>
<p><img
        loading="lazy"
        src="/posts/2023-05-02-my-development-workflow-with-alacritty-fish-tmux-nvim/images/smart-manager.png"
        type=""
        alt="Smart Manager"
        
      /></p>
<p>Where you can see the existing sessions at the bottom see <code>blog</code>, <code>dotfiles</code> etc.
If I navigate to a folder where a session already exists it will automatically attach to that session.
Else it will create a session in that folder.</p>
<p>Note that plugin uses <a href="https://github.com/ajeetdsouza/zoxide">zoxide</a> which you can think of it as a smarter <code>cd</code>.
It keeps track of which folders we visit the most. For example, I have a folder at <code>~/projects/bookmarkey/gui</code>
since I visit it so often I can go to the folder by using this command <code>z gui</code>. The script combines this with the fzf fuzzy finding tool.
Provides a nice way to search through our folders and sessions.</p>
<p>Once I am done with a session I simply detach from it and swap to another session either by <code>&lt;prefix&gt; + d</code> or using the
<code>t</code> command or even within tmux <code>&lt;prefix&gt; + shift + t</code>, which brings up that search menu.</p>
<p>Essentially this means I can have all my different projects opened and set up just how I want but they are out of &ldquo;focus&rdquo;
until I need them. Making it easier to just focus on the project I am working on.</p>
<p>This was the plugin that convinced me to start using tmux.</p>
<h3 id="config-1">Config</h3>
<p>Some parts of my config that may be interesting</p>
<pre tabindex="0"><code class="language-conf" data-lang="conf">set-option -g prefix C-a
unbind-key C-b
bind-key C-a send-prefix

unbind %
unbind &#39;&#34;&#39;
bind | split-window -h -c &#34;#{pane_current_path}&#34;
bind - split-window -v -c &#34;#{pane_current_path}&#34;
</code></pre><p>I remap the prefix key from <code>ctrl + b</code> to <code>ctrl + a</code> and to create a split using <code>-</code> for horizontal and <code>|</code> for vertical
splits (all taken from other people&rsquo;s configs ofc 😅). These mappings just make more sense in my head and also are
similar to mappings to make splits in nvim as well.</p>
<p>The <code>-c &quot;#{pane_current_path}&quot;</code> part means when we open the new pane the working directory will be same as the current
pane it is opened from.</p>
<p>I use the following plugins:</p>
<pre tabindex="0"><code class="language-conf" data-lang="conf"># List of plugins
set -g @plugin &#39;tmux-plugins/tpm&#39;
set -g @plugin &#39;aserowy/tmux.nvim&#39;
set -g @plugin &#39;joshmedeski/t-smart-tmux-session-manager&#39;
set -g @plugin &#39;ofirgall/tmux-window-name&#39;
set -g @plugin &#39;tmux-plugins/tmux-resurrect&#39;
set -g @plugin &#39;tmux-plugins/tmux-continuum&#39;
set -g @plugin &#39;ofirgall/tmux-browser&#39;

run &#39;~/.tmux/plugins/tpm/tpm&#39;
</code></pre><p>They are set up using the <a href="https://github.com/tmux-plugins/tpm">tpm</a>. Then to install any new plugins you add to your
config you can do <code>&lt;prefix&gt; + shift + I</code>.</p>
<ul>
<li>aserowy/tmux.nvim: Installed in tmux and nvim allows for easier navigation between the two</li>
<li>joshmedeski/t-smart-tmux-session-manager: We spoke about this above, easier session management</li>
<li>tmux-plugins/tmux-resurrect: This and tmux-continuum are used to save and restore sessions so they persist even after shutdown</li>
<li>ofirgall/tmux-browser: It opens a browser when you attach and closes when you detach</li>
</ul>
<p>For restoring sessions:</p>
<pre tabindex="0"><code class="language-conf" data-lang="conf">## Restore Vim sessions
set -g @resurrect-strategy-vim &#39;session&#39;
## Restore Neovim sessions
set -g @resurrect-strategy-nvim &#39;session&#39;
## Restore Panes
set -g @resurrect-capture-pane-contents &#39;on&#39;

## Restore last saved environment (automatically)
set -g @continuum-restore &#39;on&#39;
set -g @continuum-boot &#39;on&#39;
</code></pre><p>Resurrect allows us to save and restore a session, continuum will automate the saving and restoration of sessions.</p>
<h3 id="neovim">neovim</h3>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">dotfiles</summary>
  
  These dotfiles are very likely to change.
</details>

<p><a href="https://github.com/hmajid2301/starter/tree/9281544559ce93b5202fdbad9c700c8d11ba77cf">Link to config</a></p>
<p>I recently swapped from VSCode to [neovim]((<a href="https://neovim.io/">https://neovim.io/</a>) mainly just to give it ago after watching some
other developers&rsquo; workflows on Youtube such as <a href="https://www.youtube.com/c/theprimeagen">Primeagen</a>,
<a href="https://www.youtube.com/@ElijahManor">Elijah Manor</a> and <a href="https://www.youtube.com/@dreamsofcode">Dreams of Code</a>.</p>
<p>Some advantages include it being more lightweight and feels a bit more responsive. If you want to be able to customise
everything about your editor then neovim is for you. However, there are neovim distributions like LunarVim,
Astronvim and Nvchad, which come with a bunch of plugins keybindings all set up for you.</p>
<p>In my case, I ended up using <a href="https://www.lazyvim.org/">LazyVim</a> which has some nice defaults but is more barebones than
some of the distributions I listed above.</p>
<h3 id="plugins-1">Plugins</h3>
<p>Telescope is an amazing plugin we can make it even better with a few small additions.</p>
<p><a href="https://github.com/jvgrootveld/telescope-zoxide">telescope-zoxide</a> allows us to use zoxide with telescope we can swap
folders on the fly. If we add a shortcut like this:</p>
<p><code>vim.keymap.set(&quot;n&quot;, &quot;&lt;leader&gt;mm&quot;, require(&quot;telescope&quot;).extensions.zoxide.list, { desc = &quot;Change directory using zoxide&quot; })</code></p>
<p><img
        loading="lazy"
        src="/posts/2023-05-02-my-development-workflow-with-alacritty-fish-tmux-nvim/images/telescope-zoxide.png"
        type=""
        alt="Zoxide"
        
      /></p>
<p><a href="https://github.com/debugloop/telescope-undo.nvim">telescope-undo</a> allows us to see updates we have done to our file,
allowing us to revert to a previous version.</p>
<p><code>vim.keymap.set(&quot;n&quot;, &quot;&lt;leader&gt;uu&quot;, &quot;&lt;cmd&gt;Telescope undo&lt;cr&gt;&quot;, { desc = &quot;Show undoo tree&quot; })</code></p>
<p>To integrate with tmux we also install the <a href="https://github.com/aserowy/tmux.nvim">aseorwy/tmux.nvim</a> in nvim.
Now we can navigate our nvim and splits to our tmux panes. Which means we can say have a terminal open at the bottom.
Then go to it by pressing <code>ctrl + k</code>.</p>
<p><img
        loading="lazy"
        src="/posts/2023-05-02-my-development-workflow-with-alacritty-fish-tmux-nvim/images/tmux-nvim.png"
        type=""
        alt="Tmux Nvim"
        
      /></p>
<p>Finally, look at some of the keymaps (mostly nicked from ThePrimeagen):</p>
<p>Paste our contents without replace what&rsquo;s in the register.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;x&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;leader&gt;p&#34;</span><span class="p">,</span> <span class="s2">&#34;</span><span class="se">\&#34;</span><span class="s2">_dP&#34;</span><span class="p">,</span> <span class="p">{</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Paste without updating register&#34;</span> <span class="p">})</span>
</span></span></code></pre></div><p>Allows us to keep indenting or outdenting without needing to reselect.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;v&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;&#34;</span><span class="p">,</span> <span class="s2">&#34;&lt;gv&#34;</span><span class="p">,</span> <span class="p">{</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Stay in visual mode during outdent&#34;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="n">vim.keymap</span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="s2">&#34;v&#34;</span><span class="p">,</span> <span class="s2">&#34;&gt;&#34;</span><span class="p">,</span> <span class="s2">&#34;&gt;gv&#34;</span><span class="p">,</span> <span class="p">{</span> <span class="n">desc</span> <span class="o">=</span> <span class="s2">&#34;Stay in visual mode during indent&#34;</span> <span class="p">})</span>
</span></span></code></pre></div><p>At the moment my neovim setup is pretty basic but I&rsquo;m it will change as I learn more about neovim and improve my setup,
suggestions are always welcome.</p>
<h2 id="summary">Summary</h2>
<p>I hope you enjoyed this post a bit different to what I usually write but I enjoyed documenting my development workflow.
I will likely add more posts in the future going into my Linux setup and also other tools I&rsquo;m to become more efficient.
Such vim binding on Firefox. Basically I&rsquo;m trying to be more keyboard driven.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://www.sean.sh/log/when-an-alias-should-actually-be-an-abbr/">https://www.sean.sh/log/when-an-alias-should-actually-be-an-abbr/</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://arcolinux.com/everthing-you-need-to-know-about-tmux-panes/">https://arcolinux.com/everthing-you-need-to-know-about-tmux-panes/</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>My Dotfiles</title>
      <link>https://haseebmajid.dev/posts/2023-04-30-my-dotfiles/</link>
      <pubDate>Sun, 30 Apr 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-04-30-my-dotfiles/</guid>
      <description>&lt;p&gt;This post is a quick introduction to
&lt;a href=&#34;https://gitlab.com/hmajid2301/dotfiles/-/tree/af7ec81b7ad48af758f73233a7b2a0461f95e285&#34;&gt;dotfiles&lt;/a&gt;, accurate as of 25th of April 2023.&lt;/p&gt;
&lt;details
  class=&#34;notice caution&#34;
  open=&#34;true&#34;
&gt;
    &lt;summary class=&#34;notice-title&#34;&gt;out of date&lt;/summary&gt;
  
  These dotfiles are out of date check out my updated ones &lt;a href=&#34;https://haseebmajid.dev/posts/2023-05-04-my-dotfiles-ii/&#34;&gt;here&lt;/a&gt;
&lt;/details&gt;

&lt;h1 id=&#34;dotfiles&#34;&gt;Dotfiles&lt;/h1&gt;
&lt;p&gt;&amp;#x1f3e0; My dotfiles setup using &lt;a href=&#34;https://github.com/anishathalye/dotbot/&#34;&gt;Dotbot&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;install&#34;&gt;Install&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;#x1f525; I wouldn&amp;rsquo;t recommend just blinding using my dotfiles. They are setup for my specific use-case. I think you&amp;rsquo;re better picking and choosing what you like &amp;#x1f604;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;git clone git@github.com:hmajid2301/dotfiles.git
cd dotfiles
make install profile=arch
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;system-overview&#34;&gt;System Overview&lt;/h2&gt;
&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2023-04-30-my-dotfiles/images/quick_settings.png&#34;
        type=&#34;&#34;
        alt=&#34;Quick Settings&#34;
        
      /&gt;
&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2023-04-30-my-dotfiles/images/tmux.png&#34;
        type=&#34;&#34;
        alt=&#34;Tmux&#34;
        
      /&gt;&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>This post is a quick introduction to
<a href="https://gitlab.com/hmajid2301/dotfiles/-/tree/af7ec81b7ad48af758f73233a7b2a0461f95e285">dotfiles</a>, accurate as of 25th of April 2023.</p>
<details
  class="notice caution"
  open="true"
>
    <summary class="notice-title">out of date</summary>
  
  These dotfiles are out of date check out my updated ones <a href="/posts/2023-05-04-my-dotfiles-ii/">here</a>
</details>

<h1 id="dotfiles">Dotfiles</h1>
<p>&#x1f3e0; My dotfiles setup using <a href="https://github.com/anishathalye/dotbot/">Dotbot</a>.</p>
<h2 id="install">Install</h2>
<blockquote>
<p>&#x1f525; I wouldn&rsquo;t recommend just blinding using my dotfiles. They are setup for my specific use-case. I think you&rsquo;re better picking and choosing what you like &#x1f604;.</p>
</blockquote>
<pre tabindex="0"><code>git clone git@github.com:hmajid2301/dotfiles.git
cd dotfiles
make install profile=arch
</code></pre><h2 id="system-overview">System Overview</h2>
<p><img
        loading="lazy"
        src="/posts/2023-04-30-my-dotfiles/images/quick_settings.png"
        type=""
        alt="Quick Settings"
        
      />
<img
        loading="lazy"
        src="/posts/2023-04-30-my-dotfiles/images/tmux.png"
        type=""
        alt="Tmux"
        
      /></p>
<ul>
<li>OS: Arch Linux</li>
<li>DE: Gnome</li>
<li>Shell: Fish
<ul>
<li>Prompt: <a href="https://starship.rs/">Starship</a></li>
</ul>
</li>
<li>Terminal: Alacritty
<ul>
<li>Editor: Neovim (using <a href="https://astronvim.com/">astronvim</a> config)</li>
</ul>
</li>
<li>Colorscheme: <a href="https://draculatheme.com/">Dracula</a></li>
<li>Icons: <a href="https://github.com/vinceliuice/Tela-circle-icon-theme">Tela-circle-dracula-dark</a></li>
<li>Fonts: <a href="https://www.monolisa.dev/">Mono Lisa</a></li>
</ul>
<h3 id="extensions">Extensions</h3>
<p>I use the following Gnome extension. You can find the config for the extensions in this <a href="gnome/settings.ini">massive file here</a>.</p>
<ul>
<li><a href="https://extensions.gnome.org/extension/5338/aylurs-widgets/">Aylurs Widgets</a></li>
<li><a href="https://extensions.gnome.org/extension/3843/just-perfection/">Just Perfection</a></li>
<li><a href="https://extensions.gnome.org/extension/4451/logo-menu/">Logo Menu</a></li>
<li><a href="https://extensions.gnome.org/extension/3193/blur-my-shell/">Blur my Shell</a></li>
<li><a href="https://github.com/pop-os/shell">Pop Shell</a>
<ul>
<li>Used for tiling</li>
</ul>
</li>
<li><a href="https://extensions.gnome.org/extension/5237/rounded-window-corners/">Rounded Window Corners</a></li>
<li><a href="https://extensions.gnome.org/extension/5279/pano/">Pano</a></li>
<li><a href="https://extensions.gnome.org/extension/615/appindicator-support/">AppIndicator</a></li>
<li><a href="https://extensions.gnome.org/extension/1010/archlinux-updates-indicator/">Updates Indicator</a></li>
</ul>
<h3 id="top-bar">Top Bar</h3>
<p><img
        loading="lazy"
        src="/posts/2023-04-30-my-dotfiles/images/topbar.png"
        type=""
        alt="Top Bar"
        
      /></p>
<ul>
<li><a href="https://gitlab.com/hmajid2301/dotfiles/-/blob/93133f7e829409a4a4c943ef38f22ffe2f5c3508/gnome/settings.ini#L763-942">Aylurs extension config</a></li>
<li>Rest of the top bar is configured using css <a href="themes/my_theme/gnome-shell/gnome-shell.css">here</a></li>
</ul>
<h3 id="applications">Applications</h3>
<p>I basically just installed every package from <a href="https://github.com/ibraheemdev/modern-unix">Modern Unix</a>.
You can find a full list of all the packages I &ldquo;use&rdquo; <a href="https://gitlab.com/hmajid2301/dotfiles/-/blob/main/meta/configs/packages.arch.yaml#L2-48">here</a>.</p>
<p>CLI tools that I use often include:</p>
<ul>
<li><a href="https://github.com/junegunn/fzf">fzf</a>: Fuzzy search tool,Especially for reverse search in my terminal with <a href="https://github.com/PatrickF1/fzf.fish">fish shell</a></li>
<li><a href="https://github.com/ajeetdsouza/zoxide">zoxide</a>: Smarter cd tool, integrated well with fzf, nvim and tmux</li>
<li><a href="https://github.com/ogham/exa">exa</a>: ls replacement, used with <a href="https://github.com/gazorby/fish-exa">exa aliases</a></li>
<li><a href="https://github.com/BurntSushi/ripgrep">ripgrep</a>: A faster <code>grep</code></li>
<li><a href="https://github.com/jesseduffield/lazygit">lazygit</a>: A TUI based git management client</li>
<li><a href="https://github.com/denisidoro/navi">navi</a>: Interactive cheat sheet</li>
</ul>
<h3 id="wallpaper">Wallpaper</h3>
<p><img
        loading="lazy"
        src="/posts/2023-04-30-my-dotfiles/images/wallpaper.gif"
        type=""
        alt="Wallpaper GIF"
        
      /></p>
<p>I wanted to have wallpaper that changes with the day, I slightly changed the <a href="https://github.com/hexive/sunpaper">sunpaper script</a>.
Which is a great script because it changes depending on the time of day i.e. when sunsets/rises.</p>
<h2 id="neovim">Neovim</h2>
<h3 id="screenshots">Screenshots</h3>
<p><img
        loading="lazy"
        src="/posts/2023-04-30-my-dotfiles/images/neovim.png"
        type=""
        alt="Neovim"
        
      />
<img
        loading="lazy"
        src="/posts/2023-04-30-my-dotfiles/images/neovim_telescope.png"
        type=""
        alt="Neovim Telescope"
        
      />
<img
        loading="lazy"
        src="/posts/2023-04-30-my-dotfiles/images/neovim_noice.png"
        type=""
        alt="Neovim Noice"
        
      /></p>
<p>I have started using nvim as my default editor (IDE?). It uses <a href="astronvim.com/">astronvim</a> as
the base config and adds a few plugins on top.</p>
<ul>
<li><a href="https://github.com/folke/trouble.nvim">trouble</a>: For showing all lsp diagnostic issues in a file</li>
<li><a href="https://github.com/jvgrootveld/telescope-zoxide">telescope zoxide</a>: For showing all lsp diagnostic issues in a file</li>
</ul>
<p>Some of the nvim setup was heavily inspired by this <a href="https://github.com/colevoss/neovoss">repo</a>.
Including the status bar.</p>
<p>Check out this cool place for tracking my neovim setup:</p>
<ul>
<li><a href="https://dotfyle.com/hmajid2301/dotfiles-nvim">https://dotfyle.com/hmajid2301/dotfiles-nvim</a></li>
</ul>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://www.dropbox.com/sh/rqs2zce3ugf1dz2/AABam3J8BF5WOCvmYjVSXWKIa?dl=0">Dropbox with extra assets</a></li>
<li><a href="https://www.flaticon.com/free-icons/dot" title="dot icons">Dot icons created by Roundicons - Flaticon</a></li>
<li><a href="https://old.reddit.com/r/wallpapers/comments/3ueq55/lakeside_day_night_transition_credit_louis_coyle/">Wallpaper</a></li>
</ul>
<h3 id="inspired-by">Inspired By</h3>
<ul>
<li><a href="https://github.com/lime-desu/dootsfile">https://github.com/lime-desu/dootsfile</a></li>
<li><a href="https://github.com/ghostx31/dotfiles/tree/37587b043f277ff5831ce5f1a3287fbaec1d9fe3">https://github.com/ghostx31/dotfiles/tree/37587b043f277ff5831ce5f1a3287fbaec1d9fe3</a></li>
<li><a href="https://github.com/Anant-mishra1729/Village-Linux-rice">https://github.com/Anant-mishra1729/Village-Linux-rice</a></li>
<li><a href="https://github.com/colevoss/dotfiles">https://github.com/colevoss/dotfiles</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Get Code Coverage From Playwright Tests in a Sveltekit App</title>
      <link>https://haseebmajid.dev/posts/2023-04-15--how-to-get-code-coverage-from-playwright-tests-in-a-sveltekit-app-/</link>
      <pubDate>Sat, 15 Apr 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-04-15--how-to-get-code-coverage-from-playwright-tests-in-a-sveltekit-app-/</guid>
      <description>&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2023-04-15--how-to-get-code-coverage-from-playwright-tests-in-a-sveltekit-app-/images/code_coverage.jpg&#34;
        type=&#34;&#34;
        alt=&#34;Code Coverage Meme&#34;
        
      /&gt;&lt;/p&gt;
&lt;p&gt;In this post, I will show you how to get code coverage reports from your Playwright tests in your SvelteKit app.
So let&amp;rsquo;s imagine we are starting with the basic SvelteKit template. First, we need to install:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;npm i -D vite-plugin-istanbul
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We need the vite plugin to instrument our code using Istanbul.
Istanbul is a tool which allows us to instrument our code such that it can determine which lines were covered
by our tests.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><img
        loading="lazy"
        src="/posts/2023-04-15--how-to-get-code-coverage-from-playwright-tests-in-a-sveltekit-app-/images/code_coverage.jpg"
        type=""
        alt="Code Coverage Meme"
        
      /></p>
<p>In this post, I will show you how to get code coverage reports from your Playwright tests in your SvelteKit app.
So let&rsquo;s imagine we are starting with the basic SvelteKit template. First, we need to install:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">npm i -D vite-plugin-istanbul
</span></span></code></pre></div><p>We need the vite plugin to instrument our code using Istanbul.
Istanbul is a tool which allows us to instrument our code such that it can determine which lines were covered
by our tests.</p>
<h2 id="viteconfigts">vite.config.ts</h2>
<p>First, let&rsquo;s update our <code>vite.config.ts</code> (or <code>.js</code>) file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">sveltekit</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;@sveltejs/kit/vite&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">defineConfig</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;vitest/config&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">istanbul</span> <span class="kr">from</span> <span class="s2">&#34;vite-plugin-istanbul&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">defineConfig</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nx">build</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">sourcemap</span>: <span class="kt">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nx">plugins</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="nx">sveltekit</span><span class="p">(),</span>
</span></span><span class="line hl"><span class="cl">    <span class="nx">istanbul</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">      <span class="nx">include</span><span class="o">:</span> <span class="s2">&#34;src/*&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">exclude</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;node_modules&#34;</span><span class="p">,</span> <span class="s2">&#34;test/&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">      <span class="nx">extension</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;.ts&#34;</span><span class="p">,</span> <span class="s2">&#34;.svelte&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">      <span class="nx">requireEnv</span>: <span class="kt">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">forceBuildInstrument</span>: <span class="kt">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">}),</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nx">test</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">include</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;src/**/*.{test,spec}.{js,ts}&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><blockquote>
<p>forceBuildInstrument - Optional boolean to enforce the plugin to add instrumentation in build mode. Defaults to false.</p>
</blockquote>
<p>In theory, we should just be able to use <code>requireEnv: true</code> and then pass the <code>VITE_COVERAGE=true</code> environment variable.
To our playwright tests job, so instead I just set the <code>forceBuildInstrument</code>. Now, this always instrument our builds with Istanbul.
However, we can do something like <code>process.env.NODE_ENV === &quot;test&quot;</code>. Which will only run when <code>NODE_ENV</code> is <code>test</code>.
In this example, we will keep it simple and leave it as is.</p>
<h2 id="tests">tests</h2>
<p>Now let&rsquo;s go to our <code>tests</code> folder we need to add a new file called <code>baseFixtures.ts</code> [1], which looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">import</span> <span class="o">*</span> <span class="kr">as</span> <span class="nx">fs</span> <span class="kr">from</span> <span class="s1">&#39;fs&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="o">*</span> <span class="kr">as</span> <span class="nx">path</span> <span class="kr">from</span> <span class="s1">&#39;path&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="o">*</span> <span class="kr">as</span> <span class="nx">crypto</span> <span class="kr">from</span> <span class="s1">&#39;crypto&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">test</span> <span class="kr">as</span> <span class="nx">baseTest</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;@playwright/test&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">istanbulCLIOutput</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">cwd</span><span class="p">(),</span> <span class="s1">&#39;.nyc_output&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kd">function</span> <span class="nx">generateUUID</span><span class="p">()</span><span class="o">:</span> <span class="kt">string</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="nx">crypto</span><span class="p">.</span><span class="nx">randomBytes</span><span class="p">(</span><span class="mi">16</span><span class="p">).</span><span class="nx">toString</span><span class="p">(</span><span class="s1">&#39;hex&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="nx">test</span> <span class="o">=</span> <span class="nx">baseTest</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nx">context</span>: <span class="kt">async</span> <span class="p">({</span> <span class="nx">context</span> <span class="p">},</span> <span class="nx">use</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="nx">context</span><span class="p">.</span><span class="nx">addInitScript</span><span class="p">(()</span> <span class="o">=&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="nb">window</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;beforeunload&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="nb">window</span> <span class="kr">as</span> <span class="kt">any</span><span class="p">).</span><span class="nx">collectIstanbulCoverage</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">((</span><span class="nb">window</span> <span class="kr">as</span> <span class="kt">any</span><span class="p">).</span><span class="nx">__coverage__</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">      <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">promises</span><span class="p">.</span><span class="nx">mkdir</span><span class="p">(</span><span class="nx">istanbulCLIOutput</span><span class="p">,</span> <span class="p">{</span> <span class="nx">recursive</span>: <span class="kt">true</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="nx">context</span><span class="p">.</span><span class="nx">exposeFunction</span><span class="p">(</span><span class="s1">&#39;collectIstanbulCoverage&#39;</span><span class="p">,</span> <span class="p">(</span><span class="nx">coverageJSON</span>: <span class="kt">string</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="k">if</span> <span class="p">(</span><span class="nx">coverageJSON</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="nx">fs</span><span class="p">.</span><span class="nx">writeFileSync</span><span class="p">(</span><span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">istanbulCLIOutput</span><span class="p">,</span> <span class="sb">`playwright_coverage_</span><span class="si">${</span><span class="nx">generateUUID</span><span class="p">()</span><span class="si">}</span><span class="sb">.json`</span><span class="p">),</span> <span class="nx">coverageJSON</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="nx">use</span><span class="p">(</span><span class="nx">context</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="kr">const</span> <span class="nx">page</span> <span class="k">of</span> <span class="nx">context</span><span class="p">.</span><span class="nx">pages</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">evaluate</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">(</span><span class="nb">window</span> <span class="kr">as</span> <span class="kt">any</span><span class="p">).</span><span class="nx">collectIstanbulCoverage</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">((</span><span class="nb">window</span> <span class="kr">as</span> <span class="kt">any</span><span class="p">).</span><span class="nx">__coverage__</span><span class="p">)))</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="nx">expect</span> <span class="o">=</span> <span class="nx">test</span><span class="p">.</span><span class="nx">expect</span><span class="p">;</span>
</span></span></code></pre></div><p>We will use <code>test</code> and <code>expect</code> functions from this module instead of the playwright ones. As this module,
will collect the corresponding coverage files into <code>.nyc_output</code> in a JSON file.</p>
<p>Now open our tests file and update them to use the base fixture module from this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">expect</span><span class="p">,</span> <span class="nx">test</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;@playwright/test&#39;</span><span class="p">;</span>
</span></span></code></pre></div><p>to this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">expect</span><span class="p">,</span> <span class="nx">test</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;./baseFixtures.js&#34;</span><span class="p">;</span>
</span></span></code></pre></div><h2 id="run-the-tests">Run the Tests</h2>
<p>Now we can run the tests like <code>npm run test</code>, this should create a new file <code>.nyc_output</code>.
To see the actual coverage we need to use <code>nyc</code> we can do that:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">npx nyc report --report-dir ./coverage --temp-dir .nyc_output --reporter<span class="o">=</span>text --exclude-after-remap <span class="nb">false</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># output</span>
</span></span><span class="line"><span class="cl">--------------<span class="p">|</span>---------<span class="p">|</span>----------<span class="p">|</span>---------<span class="p">|</span>---------<span class="p">|</span>-------------------
</span></span><span class="line"><span class="cl">File          <span class="p">|</span> % Stmts <span class="p">|</span> % Branch <span class="p">|</span> % Funcs <span class="p">|</span> % Lines <span class="p">|</span> Uncovered Line <span class="c1">#s </span>
</span></span><span class="line"><span class="cl">--------------<span class="p">|</span>---------<span class="p">|</span>----------<span class="p">|</span>---------<span class="p">|</span>---------<span class="p">|</span>-------------------
</span></span><span class="line"><span class="cl">All files     <span class="p">|</span>     <span class="m">100</span> <span class="p">|</span>        <span class="m">0</span> <span class="p">|</span>     <span class="m">100</span> <span class="p">|</span>     <span class="m">100</span> <span class="p">|</span>                   
</span></span><span class="line"><span class="cl"> +page.svelte <span class="p">|</span>     <span class="m">100</span> <span class="p">|</span>        <span class="m">0</span> <span class="p">|</span>     <span class="m">100</span> <span class="p">|</span>     <span class="m">100</span> <span class="p">|</span> <span class="m">16</span>                
</span></span><span class="line"><span class="cl"> +page.ts     <span class="p">|</span>     <span class="m">100</span> <span class="p">|</span>      <span class="m">100</span> <span class="p">|</span>     <span class="m">100</span> <span class="p">|</span>     <span class="m">100</span> <span class="p">|</span>                   
</span></span><span class="line"><span class="cl">--------------<span class="p">|</span>---------<span class="p">|</span>----------<span class="p">|</span>---------<span class="p">|</span>---------<span class="p">|</span>-------------------
</span></span></code></pre></div><h3 id="c8">C8</h3>
<p>If we want to generate a C8 (Coberatura) report we can do:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">npx nyc report --report-dir ./coverage --temp-dir .nyc_output --reporter<span class="o">=</span>cobertura --exclude-after-remap <span class="nb">false</span>
</span></span></code></pre></div><p>This will save the code coverage data in the C8 format in the <code>coverage</code> folder.</p>
<h3 id="junit">JUnit</h3>
<p>We can also generate a JUnit file, first we need to add the following to our <code>playwright.config.ts</code> file:
<code>reporter: [[&quot;junit&quot;, { outputFile: &quot;results.xml&quot; }]]</code>. Then to generate the report we need to run
<code>PLAYWRIGHT_JUNIT_OUTPUT_NAME=results.xml npm run test</code>, we pass an environment variable to tell playwright where to save
the JUnit file.</p>
<h2 id="optional-run-in-gitlab-ci">(Optional) Run in GitLab CI</h2>
<p>If we put all of this together we can run the tests in GitLab CI and get the code coverage, JUnit and Coberatura reports like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">node</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">tests:e2e</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">npx playwright install</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">npm run test -- --reporter=junit</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">npx nyc report --report-dir ./coverage --temp-dir .nyc_output --reporter=cobertura --exclude-after-remap false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">npx nyc report --report-dir ./coverage --temp-dir .nyc_output --reporter=text --exclude-after-remap false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">coverage</span><span class="p">:</span><span class="w"> </span><span class="l">/All files[^|]*\|[^|]*\s+([\d\.]+)/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">artifacts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">reports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">junit</span><span class="p">:</span><span class="w"> </span><span class="l">results.xml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">coverage_report</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">coverage_format</span><span class="p">:</span><span class="w"> </span><span class="l">cobertura</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">coverage/cobertura-coverage.xml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">when</span><span class="p">:</span><span class="w"> </span><span class="l">always</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">test-results/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">expire_in</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w"> </span><span class="l">week</span><span class="w">
</span></span></span></code></pre></div><p>GitLab will be able to show some useful information using this code coverage data, such as lines covered in an open MR.
It can show if the code coverage has gone up or down within the MR as well.</p>
<p>That&rsquo;s it! Hopefully, this post has helped you set up retrieving code coverage from your playwright tests!</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2023-04-15--how-to-get-code-coverage-from-playwright-tests-in-a-sveltekit-app-/example">Example source code</a></li>
<li><a href="https://github.com/microsoft/playwright/discussions/20841">Playwright Discussion</a></li>
<li><a href="https://github.com/stevez/playwright-test-coverage/tree/integrate-vite-plugin-istanbul">Inspired By this repo</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Get VSCode ESLint to Sort Imports</title>
      <link>https://haseebmajid.dev/posts/2023-04-09--til-how-to-get-vscode-eslint-to-sort-imports-/</link>
      <pubDate>Sun, 09 Apr 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-04-09--til-how-to-get-vscode-eslint-to-sort-imports-/</guid>
      <description>&lt;p&gt;I use VS Code as my text editor, one of the features I really like about VS Code is that it will format our file on save.
Which saves needing to run a CLI tool to do it. For example, running &lt;code&gt;prettier&lt;/code&gt;. As part of the formatting on save you can
set an option to organise your imports as well.&lt;/p&gt;
&lt;p&gt;If you open your &lt;code&gt;settings.json&lt;/code&gt;, you can add a section like this:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I use VS Code as my text editor, one of the features I really like about VS Code is that it will format our file on save.
Which saves needing to run a CLI tool to do it. For example, running <code>prettier</code>. As part of the formatting on save you can
set an option to organise your imports as well.</p>
<p>If you open your <code>settings.json</code>, you can add a section like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;eslint.validate&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;javascript&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;svelte&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;javascriptreact&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;typescript&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;typescriptreact&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;[typescript]&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;editor.defaultFormatter&#34;</span><span class="p">:</span> <span class="s2">&#34;esbenp.prettier-vscode&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;editor.tabSize&#34;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;editor.codeActionsOnSave&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;source.organizeImports&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;source.fixAll&#34;</span><span class="p">:</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Now this format our typescript files automatically when we save it, including organising our imports.
Now this is all well and good, except when we add the following ESLint plugin
<a href="https://github.com/import-js/ESLint-plugin-import"><code>ESLint-plugin-import</code></a>.</p>
<p>This plugin lets us set our own rules for how to sort plugins and how to organise them into groups.
So our ESLint config may look something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="kr">extends</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="s2">&#34;plugin:import/errors&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;plugin:import/warnings&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;plugin:import/typescript&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nx">rules</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="s2">&#34;import/order&#34;</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;warn&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">groups</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;builtin&#34;</span><span class="p">,</span> <span class="s2">&#34;external&#34;</span><span class="p">,</span> <span class="p">[</span><span class="s2">&#34;sibling&#34;</span><span class="p">,</span> <span class="s2">&#34;parent&#34;</span><span class="p">],</span> <span class="s2">&#34;index&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="nx">pathGroups</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nx">pattern</span><span class="o">:</span> <span class="s2">&#34;$app/**&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="nx">group</span><span class="o">:</span> <span class="s2">&#34;external&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">},</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nx">pattern</span><span class="o">:</span> <span class="s2">&#34;~/**&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="nx">group</span><span class="o">:</span> <span class="s2">&#34;sibling&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="nx">alphabetize</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">order</span><span class="o">:</span> <span class="s2">&#34;asc&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nx">caseInsensitive</span><span class="o">:</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;newlines-between&#34;</span><span class="o">:</span> <span class="s2">&#34;always&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>Now this will conflict with the way VS Code will format our imports, it will format them incorrectly according to the settings we have
just set up in our ESLint config. To stop this from happening it&rsquo;s very simple we need to remove the <code>&quot;source.organizeImports&quot;: true,</code>
line from our config. Now VS Code will use just ESLint to format our imports 🎉.</p>
<p>There is one downside to this approach I have noticed, which is unused imports no longer get removed automatically, which used to happen.
But this will get fixed by pre-commit hooks.</p>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">Remove Unused Imports</summary>
  
  EDIT: I found this option which will hopefully fix the above issue <code>&quot;source.removeUnusedImports&quot;: true</code>
</details>

<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/bookmarkey/gui/-/blob/7ce4d9326b610ae16840691d16fbb82a6ec4f5ee/.ESLintrc.cjs">Example Repo</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Deploy &#39;Multiple&#39; Sites on One GitLab Page Site</title>
      <link>https://haseebmajid.dev/posts/2023-04-06-til-how-to-deploy-multiple-sites-on-one-gitlab-page-site/</link>
      <pubDate>Thu, 06 Apr 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-04-06-til-how-to-deploy-multiple-sites-on-one-gitlab-page-site/</guid>
      <description>&lt;p&gt;I have a repo which I use to store all of my conference and similar talks. This repo includes any code examples
and most importantly the slides.&lt;/p&gt;
&lt;p&gt;At the moment all the slides are RevealJS &amp;ldquo;sites&amp;rdquo;, which means it&amp;rsquo;s a presentation built in HTML. Now I would like
to have all my talks deployed to single GitLab pages site. For example something like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://hmajid2301.io/talks/an-intro-to-pocketbase/&#34;&gt;https://hmajid2301.io/talks/an-intro-to-pocketbase/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://hmajid2301.io/talks/docker-as-a-dev-tool/&#34;&gt;https://hmajid2301.io/talks/docker-as-a-dev-tool/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So how can we do this ? Simple! In our job to deploy the site we just need to add &lt;code&gt;public/docker-as-a-dev-tool&lt;/code&gt; and &lt;code&gt;public/an-intro-to-pocketbase&lt;/code&gt;
respectively. Which ever path we provide is that path we will be able to access those slides on.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I have a repo which I use to store all of my conference and similar talks. This repo includes any code examples
and most importantly the slides.</p>
<p>At the moment all the slides are RevealJS &ldquo;sites&rdquo;, which means it&rsquo;s a presentation built in HTML. Now I would like
to have all my talks deployed to single GitLab pages site. For example something like:</p>
<ul>
<li><a href="https://hmajid2301.io/talks/an-intro-to-pocketbase/">https://hmajid2301.io/talks/an-intro-to-pocketbase/</a></li>
<li><a href="https://hmajid2301.io/talks/docker-as-a-dev-tool/">https://hmajid2301.io/talks/docker-as-a-dev-tool/</a></li>
</ul>
<p>So how can we do this ? Simple! In our job to deploy the site we just need to add <code>public/docker-as-a-dev-tool</code> and <code>public/an-intro-to-pocketbase</code>
respectively. Which ever path we provide is that path we will be able to access those slides on.</p>
<p>Here is an example <code>.gitlab-ci.yml</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="c"># .gitlab-ci.yml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">pages</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">pages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">pages</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">scripts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">mkdir -p public/docker-as-a-dev-tool</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">mv docker-as-a-dev-tool public/docker-as-a-dev-tool</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">artifacts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">public</span><span class="w">
</span></span></span></code></pre></div><p>Voila that&rsquo;s it! Now we have multiple slides deployed to the same GitLab pages site.
So I can keep all of my talks nice and neat in a single repo.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/talks">My Talks Project</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Mock Classes Using Vitest</title>
      <link>https://haseebmajid.dev/posts/2023-04-01--til-mock-classes-using-vitest-/</link>
      <pubDate>Sat, 01 Apr 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-04-01--til-mock-classes-using-vitest-/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Mock Classes Using Vitest&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Recently I have been creating a SvelteKit app, when creating a new SvelteKit app you get a choice
of different things you can add. Such as using &lt;code&gt;vitest&lt;/code&gt; for unit testing.&lt;/p&gt;
&lt;p&gt;I needed to spy on/mock a method in a class, to see if it was called when a button was pressed and, it was called
with the correct arguments.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s say we have a &lt;code&gt;Button&lt;/code&gt; component which looks like this:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Mock Classes Using Vitest</strong></p>
<p>Recently I have been creating a SvelteKit app, when creating a new SvelteKit app you get a choice
of different things you can add. Such as using <code>vitest</code> for unit testing.</p>
<p>I needed to spy on/mock a method in a class, to see if it was called when a button was pressed and, it was called
with the correct arguments.</p>
<p>Let&rsquo;s say we have a <code>Button</code> component which looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-svelte" data-lang="svelte"><span class="line"><span class="cl"><span class="c">&lt;!-- button.svelte --&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">lang</span><span class="o">=</span><span class="s">&#34;ts&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="kr">import</span> <span class="p">{</span> <span class="nx">API</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;./api&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">api</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">API</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">button</span> <span class="na">on:click</span><span class="o">=</span><span class="p">{</span><span class="nx">api</span><span class="p">.</span><span class="nx">create</span><span class="p">}</span><span class="err">&gt;</span><span class="s">Press</span> <span class="na">Me</span><span class="err">!&lt;/</span><span class="na">button</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>Then the <code>api.ts</code> looks something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">class</span> <span class="nx">API</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="kr">async</span> <span class="nx">create() {</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// ... does something
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>So how do we test that the <code>create</code> method is called?
Let&rsquo;s assume we will be using the <code>svelte testing library</code> <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="c1">// __tests__/button.test.ts
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">import</span> <span class="p">{</span> <span class="nx">render</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;@testing-library/svelte&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">userEvent</span> <span class="kr">from</span> <span class="s2">&#34;@testing-library/user-event&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">describe</span><span class="p">,</span> <span class="nx">expect</span><span class="p">,</span> <span class="nx">test</span><span class="p">,</span> <span class="nx">vi</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;vitest&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">Button</span> <span class="kr">from</span> <span class="s2">&#34;../Button.svelte&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">API</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;./api&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">describe</span><span class="p">(</span><span class="s2">&#34;Button&#34;</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">test</span><span class="p">(</span><span class="s2">&#34;Successfully render Button&#34;</span><span class="p">,</span> <span class="kr">async</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="kr">const</span> <span class="nx">user</span> <span class="o">=</span> <span class="nx">userEvent</span><span class="p">.</span><span class="nx">setup</span><span class="p">();</span>
</span></span><span class="line hl"><span class="cl">		<span class="kr">const</span> <span class="nx">mock</span> <span class="o">=</span> <span class="nx">vi</span><span class="p">.</span><span class="nx">spyOn</span><span class="p">(</span><span class="nx">API</span><span class="p">.</span><span class="nx">prototype</span><span class="p">,</span> <span class="s2">&#34;create&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="kr">const</span> <span class="p">{</span> <span class="nx">getByRole</span><span class="p">,</span> <span class="nx">getByLabelText</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">render</span><span class="p">(</span><span class="nx">Button</span><span class="p">,</span> <span class="p">{</span> <span class="nx">props</span><span class="o">:</span> <span class="p">{}</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="kr">const</span> <span class="nx">button</span> <span class="o">=</span> <span class="nx">getByRole</span><span class="p">(</span><span class="s2">&#34;button&#34;</span><span class="p">,</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">&#34;Add Bookmark&#34;</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">		<span class="k">await</span> <span class="nx">user</span><span class="p">.</span><span class="nx">click</span><span class="p">(</span><span class="nx">button</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="nx">expect</span><span class="p">(</span><span class="nx">mock</span><span class="p">).</span><span class="nx">toHaveBeenCalled</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">	<span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><p>The key being this line <code>vi.spyOn(API.prototype, &quot;create&quot;)</code>, where we need to use <code>.prototype</code>.
So we can access the methods in the class. Without the <code>.prototype</code> we will not find the method
on the object itself.</p>
<p>That&rsquo;s it! Thanks for reading.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/bookmarkey/gui/-/blob/main/src/lib/components/organisms/__tests__/AddBookmarkModal.test.ts#L11">Example Test from my App</a></li>
</ul>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://testing-library.com/docs/svelte-testing-library/intro/">https://testing-library.com/docs/svelte-testing-library/intro/</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Use Multiple Auth Files in Playwright</title>
      <link>https://haseebmajid.dev/posts/2023-03-22--til-how-to-use-multiple-auth-in-playwright-/</link>
      <pubDate>Wed, 22 Mar 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-03-22--til-how-to-use-multiple-auth-in-playwright-/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Load Authenticate State in Playwright&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In this post, I will quickly show you how you can reduce boilerplate code to log in to your app in Playwright tests.
Log in to our app is a very common action that will likely be required in most of our tests.&lt;/p&gt;
&lt;p&gt;You can read this &lt;a href=&#34;https://playwright.dev/docs/auth&#34;&gt;documentation here&lt;/a&gt;, which will explain how you can set this up.
However, what do you do if you want to say test the login flow or the register flow? You cannot use the same auth
file as all the other tests as you will already be logged in.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Load Authenticate State in Playwright</strong></p>
<p>In this post, I will quickly show you how you can reduce boilerplate code to log in to your app in Playwright tests.
Log in to our app is a very common action that will likely be required in most of our tests.</p>
<p>You can read this <a href="https://playwright.dev/docs/auth">documentation here</a>, which will explain how you can set this up.
However, what do you do if you want to say test the login flow or the register flow? You cannot use the same auth
file as all the other tests as you will already be logged in.</p>
<p>It is a great way to reduce boilerplate code from our tests, which would require us to write code to log in before each test.
You could log yourself in the test but, then we are adding more boilerplate code again.</p>
<p>What you can do is create a 2nd file say called <code>playwright/.auth/not_logged_in.json</code> which looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&#34;cookies&#34;</span><span class="p">:</span> <span class="p">[],</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&#34;origins&#34;</span><span class="p">:</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>In other words there is no state, so the user will not be logged in.
Then we can do something like this in say our <code>login.test.ts</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">import</span> <span class="kr">type</span> <span class="p">{</span> <span class="nx">Page</span><span class="p">,</span> <span class="nx">expect</span><span class="p">,</span> <span class="nx">test</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;@playwright/test&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">test</span><span class="p">.</span><span class="nx">describe</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="kd">let</span> <span class="nx">page</span>: <span class="kt">Page</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line hl"><span class="cl">	<span class="nx">test</span><span class="p">.</span><span class="nx">beforeEach</span><span class="p">(</span><span class="kr">async</span> <span class="p">({</span> <span class="nx">browser</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="kr">const</span> <span class="nx">loginContext</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">browser</span><span class="p">.</span><span class="nx">newContext</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">			<span class="nx">storageState</span><span class="o">:</span> <span class="s2">&#34;playwright/.auth/not_logged_in.json&#34;</span>
</span></span><span class="line"><span class="cl">		<span class="p">});</span>
</span></span><span class="line"><span class="cl">		<span class="nx">page</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">loginContext</span><span class="p">.</span><span class="nx">newPage</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">	<span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nx">test</span><span class="p">(</span><span class="s2">&#34;Successfully login to app&#34;</span><span class="p">,</span> <span class="kr">async</span> <span class="p">({</span> <span class="nx">baseURL</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="s2">&#34;/login&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="kr">const</span> <span class="nx">email</span> <span class="o">=</span> <span class="s2">&#34;test@bookmarkey.app&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">locator</span><span class="p">(</span><span class="s1">&#39;[name=&#34;email&#34;]&#39;</span><span class="p">).</span><span class="kr">type</span><span class="p">(</span><span class="nx">email</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="kr">const</span> <span class="nx">password</span> <span class="o">=</span> <span class="s2">&#34;password@11&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">locator</span><span class="p">(</span><span class="s1">&#39;[name=&#34;password&#34;]&#39;</span><span class="p">).</span><span class="kr">type</span><span class="p">(</span><span class="nx">password</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">locator</span><span class="p">(</span><span class="s1">&#39;button[type=&#34;submit&#34;]&#39;</span><span class="p">).</span><span class="nx">click</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">		<span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">waitForURL</span><span class="p">(</span><span class="sb">`</span><span class="si">${</span><span class="nx">baseURL</span><span class="si">}</span><span class="sb">/my/collections/0`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nx">test</span><span class="p">(</span><span class="s2">&#34;Fail to login to app using incorrect credentials&#34;</span><span class="p">,</span> <span class="kr">async</span> <span class="p">({</span> <span class="nx">baseURL</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="s2">&#34;/login&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="kr">const</span> <span class="nx">email</span> <span class="o">=</span> <span class="s2">&#34;test@bookmarkey.app&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">locator</span><span class="p">(</span><span class="s1">&#39;[name=&#34;email&#34;]&#39;</span><span class="p">).</span><span class="kr">type</span><span class="p">(</span><span class="nx">email</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="kr">const</span> <span class="nx">password</span> <span class="o">=</span> <span class="s2">&#34;wrong_password&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">locator</span><span class="p">(</span><span class="s1">&#39;[name=&#34;password&#34;]&#39;</span><span class="p">).</span><span class="kr">type</span><span class="p">(</span><span class="nx">password</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">locator</span><span class="p">(</span><span class="s1">&#39;button[type=&#34;submit&#34;]&#39;</span><span class="p">).</span><span class="nx">click</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="kr">const</span> <span class="nx">toastMessage</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">locator</span><span class="p">(</span><span class="s2">&#34;.message&#34;</span><span class="p">).</span><span class="nx">innerText</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">		<span class="nx">expect</span><span class="p">(</span><span class="nx">toastMessage</span><span class="p">).</span><span class="nx">toBe</span><span class="p">(</span><span class="s2">&#34;Wrong email and password combination.&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">waitForURL</span><span class="p">(</span><span class="sb">`</span><span class="si">${</span><span class="nx">baseURL</span><span class="si">}</span><span class="sb">/login`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><p>Rather than using the &ldquo;normal&rdquo; page argument provided by Playwright, we will create our context.
Using this stateless file, will mean the user is not logged in.</p>
<p>If we still want a test to use to automatically logged in we can do:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">test</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;@playwright/test&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">test</span><span class="p">.</span><span class="nx">describe</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">test</span><span class="p">(</span><span class="s2">&#34;Successfully load collections in side bar&#34;</span><span class="p">,</span> <span class="kr">async</span> <span class="p">({</span> <span class="nx">page</span><span class="p">,</span> <span class="nx">baseURL</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="sb">`</span><span class="si">${</span><span class="nx">baseURL</span><span class="si">}</span><span class="sb">/my/collections/0`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">getByRole</span><span class="p">(</span><span class="s2">&#34;link&#34;</span><span class="p">,</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">&#34;folder closed test&#34;</span> <span class="p">}).</span><span class="nx">click</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">		<span class="k">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">waitForURL</span><span class="p">(</span><span class="sb">`</span><span class="si">${</span><span class="nx">baseURL</span><span class="si">}</span><span class="sb">/my/collections/46lfmlwhymhv6xl`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><p>That&rsquo;s it!</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/bookmarkey/gui/-/blob/55f7a6456da20a362c7b7ccf6069a9118acaa7f7/tests/auth.setup.ts">Used in the Bookmarkey Project</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Create New Posts on Hugo Using Archetypes</title>
      <link>https://haseebmajid.dev/posts/2023-03-20--how-to-create-new-posts-on-hugo-using-archetypes-/</link>
      <pubDate>Mon, 20 Mar 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-03-20--how-to-create-new-posts-on-hugo-using-archetypes-/</guid>
      <description>&lt;p&gt;In this post, I will go over how you can use Hugo&amp;rsquo;s archetypes to quickly create new posts.
I have previously used NetlifyCMS to help create new posts, but recently I have found that to be
a bit overkill for my blog.&lt;/p&gt;
&lt;p&gt;So I decided to simplify my workflow by using Hugo&amp;rsquo;s archetypes &lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. Archetypes allow us to create templates
markdown files, which is then used to create our blog posts.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this post, I will go over how you can use Hugo&rsquo;s archetypes to quickly create new posts.
I have previously used NetlifyCMS to help create new posts, but recently I have found that to be
a bit overkill for my blog.</p>
<p>So I decided to simplify my workflow by using Hugo&rsquo;s archetypes <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>. Archetypes allow us to create templates
markdown files, which is then used to create our blog posts.</p>
<h2 id="archetypes">Archetypes</h2>
<p>First, let&rsquo;s create a new archetype we will call <code>post-bundle</code></p>
<p>Create a new folder at the root of your project called <code>archetypes</code> (i.e. <code>mkdir archetypes</code>).
Then inside the <code>archetype</code> folder, create a structure that looks like this.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">├── post-bundle
</span></span><span class="line"><span class="cl">│   ├── images
</span></span><span class="line"><span class="cl">│   └── index.md
</span></span></code></pre></div><h3 id="page-bundles">Page Bundles</h3>
<p>We will be using a page bundle, so we keep all the content related to our post in a single folder, like the images <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>.
All we need to do is create an <code>index.md</code> which will contain the content we show on our Hugo site.</p>
<h3 id="indexmd">index.md</h3>
<p>Our <code>index.md</code> file will look something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl">---
</span></span><span class="line"><span class="cl">title: {{ slicestr (replace .Name &#34;-&#34; &#34; &#34;) 11 | title }}
</span></span><span class="line"><span class="cl">date: {{ dateFormat &#34;2006-01-02&#34; .Date }}
</span></span><span class="line"><span class="cl">canonicalURL: https://haseebmajid.dev/posts/{{.Name}}
</span></span><span class="line"><span class="cl">tags: []
</span></span><span class="line"><span class="cl">---
</span></span></code></pre></div><p>Let&rsquo;s break this file down:</p>
<p><code>title: {{ slicestr (replace .Name &quot;-&quot; &quot; &quot;) 11 | title }}</code></p>
<p>Here we are using go tempting to take a variable <code>.Name</code>. For example if
<code>.Name = 2023-03-20--how-to-create-new-posts-on-hugo-using-archetypes-</code>
<code>title:  How to Create New Posts on Hugo Using Archetypes</code></p>
<p>You can see we strip the first <code>11</code> characters to strip the date of the title.
Then we move all <code>-</code> hyphens and replace them with blank spaces. Finally, we convert
it to a &ldquo;title&rdquo; case which will capitalize certain words such as <code>Hugo</code> and <code>New</code> but not <code>on</code>.</p>
<p>Next, we have:</p>
<p><code>date: {{ dateFormat &quot;2006-01-02&quot; .Date }}</code></p>
<p>This is just formatting the date to the format we want, in my case I want the date to be <code>YYYY-MM-DD</code> <sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>.</p>
<h2 id="post">Post</h2>
<p>Now that we have a template, let&rsquo;s look at how we can create a new post. We will use a script to create new posts.
Let&rsquo;s create a new script at <code>script/add</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env bash
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="nv">TITLE</span><span class="o">=</span><span class="si">${</span><span class="nv">1</span><span class="k">:-</span><span class="si">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">TITLE_SLUG</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span><span class="nb">echo</span> -n <span class="s2">&#34;</span><span class="nv">$TITLE</span><span class="s2">&#34;</span> <span class="p">|</span> sed -e <span class="s1">&#39;s/[^[:alnum:]]/-/g&#39;</span> <span class="p">|</span> tr -s <span class="s1">&#39;-&#39;</span> <span class="p">|</span> tr A-Z a-z<span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">DATE</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span>date +<span class="s2">&#34;%F&#34;</span><span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">SLUG</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$DATE</span><span class="s2">-</span><span class="nv">$TITLE_SLUG</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">git checkout -b <span class="s2">&#34;</span><span class="nv">$SLUG</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">hugo new --kind post-bundle posts/<span class="nv">$SLUG</span>
</span></span></code></pre></div><p>This script takes a title as input and then converts it into a string we can use as the folder name and this
will be the <code>.Name</code> variable. For example <code>scripts/add &quot;How to Create New Posts on Hugo using Archetypes&quot;</code>
will become <code>2023-03-18--how-to-create-new-posts-on-hugo-using-archetypes-</code>.</p>
<p>The script assumes you have a folder called <code>content/posts</code> where all of your posts are stored.
I have all of my blog posts in <code>content/posts</code>, and then I have another folder for my talks at <code>content/talks</code>.</p>
<p>In my case, I use <code>go-task</code> <sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> which can be used as a Makefile alternative. I have the following entry in my
<code>Taskfile.yml</code> which looks like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="w">  </span><span class="nt">new_post</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">cmds</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">scripts/add &#34;{{.CLI_ARGS}}&#34;</span><span class="w">
</span></span></span></code></pre></div><p>Finally, I can do something like this; <code>task new_post -- &quot;How to Create New Posts on Hugo using Archetypes&quot;</code>.
This creates a new bundle at <code>content/posts/2023-03-18--how-to-create-new-posts-on-hugo-using-archetypes-</code>.
With a new <code>index.md</code>, which looks like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl">---
</span></span><span class="line"><span class="cl">title: How to Create New Posts on Hugo Using Archetypes 
</span></span><span class="line"><span class="cl">date: 2023-03-18
</span></span><span class="line"><span class="cl">canonicalURL: https://haseebmajid.dev/posts/2023-03-18--how-to-create-new-posts-on-hugo-using-archetypes-
</span></span><span class="line"><span class="cl">tags: []
</span></span><span class="line"><span class="cl">---
</span></span></code></pre></div><h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://randomgeekery.org/post/2017/07/hugo-archetype-templates/">Inspired by this post</a></li>
</ul>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://gohugo.io/content-management/archetypes/">https://gohugo.io/content-management/archetypes/</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://gohugo.io/content-management/page-bundles/">https://gohugo.io/content-management/page-bundles/</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p><a href="https://gohugo.io/functions/format/#hugo-date-and-time-templating-reference">https://gohugo.io/functions/format/#hugo-date-and-time-templating-reference</a>&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p><a href="https://taskfile.dev/">https://taskfile.dev/</a>&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Set a Relationship on Golang Pocketbase</title>
      <link>https://haseebmajid.dev/posts/2023-03-08--til-how-to-set-a-relationship-on-golang-pocketbase-/</link>
      <pubDate>Wed, 08 Mar 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-03-08--til-how-to-set-a-relationship-on-golang-pocketbase-/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Set a Relationship on Golang Pocketbase&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://pocketbase.io/&#34;&gt;Pocketbase&lt;/a&gt; is a backend as a service, it is very nice because it is written in Golang
it can compile down to a single binary. It also allows us to extend it using Golang and use it as a framework.
In my case, I needed some extra logic before adding data to the database so I decided to extend Pocketbase
and write some of my business logic.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Set a Relationship on Golang Pocketbase</strong></p>
<p><a href="https://pocketbase.io/">Pocketbase</a> is a backend as a service, it is very nice because it is written in Golang
it can compile down to a single binary. It also allows us to extend it using Golang and use it as a framework.
In my case, I needed some extra logic before adding data to the database so I decided to extend Pocketbase
and write some of my business logic.</p>
<p>Part of the code involved adding some entries to the Database including a relationship. However, I noticed that the relationship
looked different in the Pocketbase GUI as compared with the ones I created using the Pocketbase client in the SvelteKit part of my app.</p>
<p>The other relationships showed up a bit differently, they didn&rsquo;t include the id of the relationship (as a foreign key) but rather a field
on the other collection. In the example below the relationship field I am referencing is the <code>bookmark_metadata</code>. Which uses the <code>title</code> field.</p>
<p><img
        loading="lazy"
        src="/posts/2023-03-08--til-how-to-set-a-relationship-on-golang-pocketbase-/images/bookmark.png"
        type=""
        alt="Bookmark"
        
      /></p>
<p>Where the bookmark metadata may look like:</p>
<p><img
        loading="lazy"
        src="/posts/2023-03-08--til-how-to-set-a-relationship-on-golang-pocketbase-/images/bookmark_metadata.png"
        type=""
        alt="Bookmark Metadata"
        
      /></p>
<p>We can achieve this using the following code:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-golang" data-lang="golang"><span class="line"><span class="cl"><span class="nx">bookmarkRecord</span> <span class="o">:=</span> <span class="nx">models</span><span class="p">.</span><span class="nf">NewRecord</span><span class="p">(</span><span class="nx">collection</span><span class="p">)</span>
</span></span><span class="line hl"><span class="cl"><span class="nx">bookmarkRecord</span><span class="p">.</span><span class="nf">Set</span><span class="p">(</span><span class="s">&#34;bookmark_metadata&#34;</span><span class="p">,</span> <span class="p">[]</span><span class="kt">string</span><span class="p">{</span><span class="nx">metadataRecord</span><span class="p">.</span><span class="nx">Id</span><span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="nx">bookmarkRecord</span><span class="p">.</span><span class="nf">Set</span><span class="p">(</span><span class="s">&#34;favourite&#34;</span><span class="p">,</span> <span class="kc">false</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">bookmarkRecord</span><span class="p">.</span><span class="nf">Set</span><span class="p">(</span><span class="s">&#34;collection&#34;</span><span class="p">,</span> <span class="nx">collectionID</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">bookmarkRecord</span><span class="p">.</span><span class="nf">Set</span><span class="p">(</span><span class="s">&#34;custom_order&#34;</span><span class="p">,</span> <span class="nx">math</span><span class="p">.</span><span class="nx">MaxInt32</span><span class="p">)</span>
</span></span></code></pre></div><p>The key being that we set <code>bookmark_metadata</code> as a slice (array).
However, if we did:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-golang" data-lang="golang"><span class="line"><span class="cl"><span class="nx">bookmarkRecord</span><span class="p">.</span><span class="nf">Set</span><span class="p">(</span><span class="s">&#34;bookmark_metadata&#34;</span><span class="p">,</span> <span class="nx">metadataRecord</span><span class="p">.</span><span class="nx">Id</span><span class="p">)</span>
</span></span></code></pre></div><p>In the GUI the record would look like this:</p>
<p><img
        loading="lazy"
        src="/posts/2023-03-08--til-how-to-set-a-relationship-on-golang-pocketbase-/images/bookmark_id.png"
        type=""
        alt="Bookmark ID"
        
      /></p>
<p>Both options will work for setting up a relationship between two collections. I just wanted to show you how you can set it
so it shows up like relationship created via the JS SDK. You can read more about creating records with Golang
<a href="https://pocketbase.io/docs/record-methods/#create-new-record">here</a>.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Run Parallel Jobs on Gitlab CI (Different Stages)</title>
      <link>https://haseebmajid.dev/posts/2023-03-03-til-how-to-run-parallel-jobs-on-gitlab-ci/</link>
      <pubDate>Fri, 03 Mar 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-03-03-til-how-to-run-parallel-jobs-on-gitlab-ci/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Run Parallel Jobs on Gitlab CI (Different Stages)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you are familiar with Gitlab CI you probably know that jobs in the same stages will run in parallel.
However you can also run jobs in different stages at the same time. Let&amp;rsquo;s see our &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; looks like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;stages&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;test&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;deploy&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;format&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;stage&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;test&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;script&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;task format&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;lint&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;stage&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;test&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;script&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;task lint&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;deploy:preview&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;stage&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;deploy&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;only&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;merge_request&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;docker&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;script&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;task deploy&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So for my use case when a user creates a merge request, I want to run some tests against the code,
such as linting and formatting. But also deploy my app to a preview environment. Whilst these jobs
belong to different stages.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Run Parallel Jobs on Gitlab CI (Different Stages)</strong></p>
<p>If you are familiar with Gitlab CI you probably know that jobs in the same stages will run in parallel.
However you can also run jobs in different stages at the same time. Let&rsquo;s see our <code>.gitlab-ci.yml</code> looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">deploy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">format</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">task format</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">lint</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">task lint</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">deploy:preview</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">deploy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_request</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">docker</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">task deploy</span><span class="w">
</span></span></span></code></pre></div><p>So for my use case when a user creates a merge request, I want to run some tests against the code,
such as linting and formatting. But also deploy my app to a preview environment. Whilst these jobs
belong to different stages.</p>
<p>The deploy job shouldn&rsquo;t really depend on the jobs in the <code>test</code> stage as we still want to deploy our
app regardless so we can test in the preview environment.</p>
<p>We can do that using <code>needs</code> <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> keywords, so our <code>deploy:preview</code> job will now look like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">deploy:preview</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">deploy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_request</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">  </span><span class="nt">needs</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">docker</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">task deploy</span><span class="w">
</span></span></span></code></pre></div><p>Now our job will run at the same time as the <code>test</code> stage jobs.
The <code>needs</code> keyword is used to run our job out of order.</p>
<blockquote>
<p>An empty array ([]), to set the job to start as soon as the pipeline is created. - Gitlab CI Docs</p>
</blockquote>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://docs.gitlab.com/ee/ci/yaml/#needs">https://docs.gitlab.com/ee/ci/yaml/#needs</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Setup Netlify Deployment Notifications on Discord with Pipedream</title>
      <link>https://haseebmajid.dev/posts/2023-01-26-how-to-setup-netlify-deployment-notifications-on-discord-with-pipedream/</link>
      <pubDate>Thu, 26 Jan 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-01-26-how-to-setup-netlify-deployment-notifications-on-discord-with-pipedream/</guid>
      <description>&lt;p&gt;Hi everyone, I recently went about setting up notifications about my Netlify deploys in Discord. Netlify does have an official way to do
however, you cannot use it on the free tier.&lt;/p&gt;
&lt;p&gt;So I looked at other solutions, and I eventually discovered a SaaS product called &lt;a href=&#34;pipedream.com/&#34;&gt;PipeDream&lt;/a&gt;. It is a website which allows
us to connect different components without needing to write any code. Such as what to do when we receive a webhook. There are 100s
of different apps, triggers and actions. What we want to end up with is something like this:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Hi everyone, I recently went about setting up notifications about my Netlify deploys in Discord. Netlify does have an official way to do
however, you cannot use it on the free tier.</p>
<p>So I looked at other solutions, and I eventually discovered a SaaS product called <a href="pipedream.com/">PipeDream</a>. It is a website which allows
us to connect different components without needing to write any code. Such as what to do when we receive a webhook. There are 100s
of different apps, triggers and actions. What we want to end up with is something like this:</p>
<p><img
        loading="lazy"
        src="/posts/2023-01-26-how-to-setup-netlify-deployment-notifications-on-discord-with-pipedream/images/discord.png"
        type=""
        alt="Discord"
        
      /></p>
<p>So let&rsquo;s take a look at what we need to do to post a discord message based on if our Netlify build fails or passes.
First, we need to create a new workflow, the workflow I have created looks like this for:</p>
<p><img
        loading="lazy"
        src="/posts/2023-01-26-how-to-setup-netlify-deployment-notifications-on-discord-with-pipedream/images/pipedream.png"
        type=""
        alt="PipeDream Workflow"
        
      /></p>
<h2 id="triggers">Triggers</h2>
<p>First, we setup PipeDream to trigger when a deployment is successful of my website/app, like so:</p>
<p><img
        loading="lazy"
        src="/posts/2023-01-26-how-to-setup-netlify-deployment-notifications-on-discord-with-pipedream/images/pipedream_trigger.png"
        type=""
        alt="PipeDream Trigger"
        
      /></p>
<p>This sets up a webhook from Netlify to this PipeDream workflow. So every time the deployment succeeds on Netlify
it will trigger this Pipedream workflow. We can even see the data that was sent for specific webhooks. Which looks like this:</p>
<p><a href="images/pipedream_netlify_webhook.png">PipeDream Trigger Data</a></p>
<h2 id="condition">Condition</h2>
<p>Since my app will also build for deploy previews on branches and I only want messages for my production deploys i.e.
on my <code>main</code> branch on GitLab. I will next add a simple condition which checks if the branch name in the trigger
is <code>main</code> and then carry on, else stop the workflow. Since this is a variable and will vary on builds we can refer to it as
<code>{{steps.trigger.event.branch}}</code> in condition check, to compare branch name with <code>main</code>.</p>
<h2 id="format-data">Format Data</h2>
<p>Next, since I want to send the data as an embed to format it nicely it is recommended to use NodeJS to format ours into a nice JSON
object which the discord component can then use. Yes, this does mean we can execute code in a PipeDream workflow.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="c1">// To use previous step data, pass the `steps` object to the run() function
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">export</span> <span class="k">default</span> <span class="nx">defineComponent</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="kr">async</span> <span class="nx">run</span><span class="p">({</span> <span class="nx">steps</span><span class="p">,</span> <span class="nx">$</span> <span class="p">})</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Return data to use it in future steps
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="k">return</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;color&#34;</span><span class="o">:</span><span class="mi">3066993</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;title&#34;</span><span class="o">:</span><span class="s2">&#34;🚀 Successfully deployed to Netlify&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;description&#34;</span><span class="o">:</span> <span class="s2">&#34;Bookmarkey GUI&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;fields&#34;</span><span class="o">:</span><span class="p">[</span>
</span></span><span class="line"><span class="cl">          <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;name&#34;</span><span class="o">:</span><span class="s2">&#34;Commit&#34;</span><span class="p">,</span> 
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;value&#34;</span><span class="o">:</span><span class="sb">`[\`</span><span class="si">${</span><span class="nx">steps</span><span class="p">.</span><span class="nx">trigger</span><span class="p">.</span><span class="nx">event</span><span class="p">.</span><span class="nx">commit_ref</span><span class="p">.</span><span class="nx">substring</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">8</span><span class="p">)</span><span class="si">}</span><span class="sb">\`](</span><span class="si">${</span><span class="nx">steps</span><span class="p">.</span><span class="nx">trigger</span><span class="p">.</span><span class="nx">event</span><span class="p">.</span><span class="nx">commit_url</span><span class="si">}</span><span class="sb">)`</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;inline&#34;</span><span class="o">:</span><span class="kc">true</span>
</span></span><span class="line"><span class="cl">          <span class="p">},</span>
</span></span><span class="line"><span class="cl">          <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;name&#34;</span><span class="o">:</span><span class="s2">&#34;Branch&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;value&#34;</span><span class="o">:</span><span class="s2">&#34;[`main`](https://gitlab.com/bookmarkey/gui)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;inline&#34;</span><span class="o">:</span><span class="kc">true</span>
</span></span><span class="line"><span class="cl">          <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span></code></pre></div><p>In this case, the only thing we are templating is the commit ref and commit URL, which we get from previous parts of the workflow
i.e. <code>${steps.trigger.event.commit_url}</code> could be <code>https://gitlab.com/bookmarkey/gui/commit/eb5f80376e7c8bc2d33e9e0cf6fc1d3248f2ce0f</code>.</p>
<h2 id="discord-message">Discord Message</h2>
<p>Finally, we configure the discord message itself we set up which channel and server the message should go to.
In my case since I want to use a Discord embed I picked the advanced discord message.</p>
<p>To use the formatted data above we can refer to it as <code>{{steps.format_data.$return_value}}</code>.
I also set the username and the avatar URL which is what shows up in our discord message.</p>
<p><img
        loading="lazy"
        src="/posts/2023-01-26-how-to-setup-netlify-deployment-notifications-on-discord-with-pipedream/images/discord.png"
        type=""
        alt="Discord"
        
      /></p>
<h2 id="thats-it">That&rsquo;s It!</h2>
<p>So that it&rsquo;s! Now I also created a very similar workflow when my Netlify deployment fails.
I couldn&rsquo;t find any easy way to turn it into one workflow, so I just went with the easiest solution.</p>
<p>Also, it&rsquo;s a shame at the moment we cannot store our PipeDream workflows in git or code.
Hopefully, this is a feature that&rsquo;ll come soon and we can deploy our workflows from CI
and track changes using version control!</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to fix `structuredClone` to work on Netlify SvelteKit Site</title>
      <link>https://haseebmajid.dev/posts/2023-01-23-til-how-to-fix-structuredclone-to-work-on-netlify-sveltekit-site/</link>
      <pubDate>Mon, 23 Jan 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-01-23-til-how-to-fix-structuredclone-to-work-on-netlify-sveltekit-site/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to fix &lt;code&gt;structuredClone&lt;/code&gt; to work on Netlify SvelteKit Site&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Recently I was working on my SvelteKit site and added some code like so to my &lt;code&gt;hook.server.ts&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;try&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;event&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;locals&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;pb&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;authStore&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;isValid&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;event&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;locals&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;pb&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;collection&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;users&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;).&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;authRefresh&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line hl&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nx&#34;&gt;event&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;locals&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;user&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;structuredClone&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;event&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;locals&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;pb&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;authStore&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;model&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;catch&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;failed to refresh auth&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;event&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;locals&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;user&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;undefined&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;event&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;locals&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;pb&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;authStore&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;clear&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I needed to add the &lt;code&gt;structuredClone&lt;/code&gt; function so that only POJO (plain old javascript objects) would get stored in the &lt;code&gt;event.locals.users&lt;/code&gt;
variable. However when I deployed this to production in Netlify. This broke my site! Specifically, it wouldn&amp;rsquo;t let the user log in.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to fix <code>structuredClone</code> to work on Netlify SvelteKit Site</strong></p>
<p>Recently I was working on my SvelteKit site and added some code like so to my <code>hook.server.ts</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">locals</span><span class="p">.</span><span class="nx">pb</span><span class="p">.</span><span class="nx">authStore</span><span class="p">.</span><span class="nx">isValid</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">await</span> <span class="nx">event</span><span class="p">.</span><span class="nx">locals</span><span class="p">.</span><span class="nx">pb</span><span class="p">.</span><span class="nx">collection</span><span class="p">(</span><span class="s2">&#34;users&#34;</span><span class="p">).</span><span class="nx">authRefresh</span><span class="p">();</span>
</span></span><span class="line hl"><span class="cl">        <span class="nx">event</span><span class="p">.</span><span class="nx">locals</span><span class="p">.</span><span class="nx">user</span> <span class="o">=</span> <span class="nx">structuredClone</span><span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">locals</span><span class="p">.</span><span class="nx">pb</span><span class="o">?</span><span class="p">.</span><span class="nx">authStore</span><span class="p">.</span><span class="nx">model</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">&#34;failed to refresh auth&#34;</span><span class="p">,</span> <span class="nx">err</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">event</span><span class="p">.</span><span class="nx">locals</span><span class="p">.</span><span class="nx">user</span> <span class="o">=</span> <span class="kc">undefined</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">event</span><span class="p">.</span><span class="nx">locals</span><span class="p">.</span><span class="nx">pb</span><span class="p">.</span><span class="nx">authStore</span><span class="p">.</span><span class="nx">clear</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>I needed to add the <code>structuredClone</code> function so that only POJO (plain old javascript objects) would get stored in the <code>event.locals.users</code>
variable. However when I deployed this to production in Netlify. This broke my site! Specifically, it wouldn&rsquo;t let the user log in.</p>
<p>If you deploy your SvelteKit app on Netlify the server-side code gets turned into functions for us automagically! I was able to work out
that <code>structuredClone</code> function broke my app because in my <code>login</code> function I could see something like:</p>
<pre tabindex="0"><code>ReferenceError: structuredClone is not defined
</code></pre><p><img
        loading="lazy"
        src="/posts/2023-01-23-til-how-to-fix-structuredclone-to-work-on-netlify-sveltekit-site/images/netlify_functions.png"
        type=""
        alt="Netlify Functions"
        
      /></p>
<p>It seems that the <code>structuredClone</code> was only added in nodejs 17 <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>. However, Netlify functions default to nodejs 16.</p>
<blockquote>
<p>For all Node.js functions deployed on or after July 11, 2022, Netlify uses Node.js 16 as the default runtime. - <a href="https://docs.netlify.com/functions/optional-configuration/?fn-language=js">https://docs.netlify.com/functions/optional-configuration/?fn-language=js</a></p>
</blockquote>
<p>Reading the docs we can see if we set the <code>AWS_LAMBDA_JS_RUNTIME</code> in the GUI to <code>nodejs18.x</code> this will force our functions to use nodejs 18.</p>
<p><img
        loading="lazy"
        src="/posts/2023-01-23-til-how-to-fix-structuredclone-to-work-on-netlify-sveltekit-site/images/netlify_build_vars.png"
        type=""
        alt="Netlify Builds Vars"
        
      /></p>
<p>And that&rsquo;s it! By setting our nodejs version to 18, we will be able to use the <code>structuredClone</code> function.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://stackoverflow.com/a/73704608/3108619">https://stackoverflow.com/a/73704608/3108619</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Autofocus on Inputs in Svelte</title>
      <link>https://haseebmajid.dev/posts/2023-01-19-til-how-to-autofocus-on-inputs-in-svelte/</link>
      <pubDate>Thu, 19 Jan 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-01-19-til-how-to-autofocus-on-inputs-in-svelte/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Autofocus on Inputs in Svelte&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In this post, I will show you how you can autofocus on input that is in a child component.
So in my use case, I want the user to click a button to add a &amp;ldquo;collection&amp;rdquo; and then it will show the input
and immediately focus on it. Which looks something like this:&lt;/p&gt;
&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2023-01-19-til-how-to-autofocus-on-inputs-in-svelte/images/autofocus.gif&#34;
        type=&#34;&#34;
        alt=&#34;Autofocus Input&#34;
        
      /&gt;&lt;/p&gt;
&lt;p&gt;So how can we do this?&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Autofocus on Inputs in Svelte</strong></p>
<p>In this post, I will show you how you can autofocus on input that is in a child component.
So in my use case, I want the user to click a button to add a &ldquo;collection&rdquo; and then it will show the input
and immediately focus on it. Which looks something like this:</p>
<p><img
        loading="lazy"
        src="/posts/2023-01-19-til-how-to-autofocus-on-inputs-in-svelte/images/autofocus.gif"
        type=""
        alt="Autofocus Input"
        
      /></p>
<p>So how can we do this?</p>
<p>Let&rsquo;s say we have a child component called <code>Input.svelte</code> which looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-svelte" data-lang="svelte"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">lang</span><span class="o">=</span><span class="s">&#34;ts&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="kr">export</span> <span class="kd">let</span> <span class="kr">type</span><span class="o">:</span> <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kr">export</span> <span class="kd">let</span> <span class="nx">name</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kr">export</span> <span class="kd">let</span> <span class="nx">ref</span>: <span class="kt">HTMLInputElement</span> <span class="o">|</span> <span class="kc">undefined</span> <span class="o">=</span> <span class="kc">undefined</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">input</span> <span class="na">bind:this</span><span class="o">=</span><span class="p">{</span><span class="nx">ref</span><span class="p">}</span> <span class="p">{</span><span class="nx">name</span><span class="p">}</span> <span class="p">{</span><span class="kr">type</span><span class="p">}</span> <span class="s">/</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>The key prop here is <code>ref</code>, which we will use to focus on our input.
<code>ref</code> is a reference to the component itself.
Then in our parent component say called <code>AddCollection.svelte</code> we can do something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-svelte" data-lang="svelte"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">lang</span><span class="o">=</span><span class="s">&#34;ts&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="kr">import</span> <span class="p">{</span> <span class="nx">tick</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;svelte&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">let</span> <span class="nx">ref</span>: <span class="kt">HTMLInputElement</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">button</span>
</span></span><span class="line"><span class="cl">	<span class="na">on:click</span><span class="o">=</span><span class="p">{</span><span class="kr">async</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line hl"><span class="cl">		<span class="k">await</span> <span class="nx">tick</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">		<span class="nx">ref</span><span class="o">?</span><span class="p">.</span><span class="nx">focus</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">	<span class="p">}}</span><span class="err">&gt;</span>
</span></span><span class="line"><span class="cl">	Add Collection
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">Input</span> <span class="na">bind:ref</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;text&#34;</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;addCollection&#34;</span> <span class="p">/&gt;</span>
</span></span></code></pre></div><details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">Bind</summary>
  
  <p>In the parent component, we want to use <code>bind:ref</code>, as we don&rsquo;t want to bind to the <code>Input</code>
but pass the prop to the child and also bind our variable to it.</p>
<p>If we do <code>bind:this={ref}</code> in <code>AddCollection.svelte</code> this will not work as far as I know.</p>

</details>

<p>That&rsquo;s it!</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li>Revelant <a href="https://stackoverflow.com/questions/57354001/how-to-focus-on-input-field-loaded-from-component-in-svelte">SO Post</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How Can We Update GitLab CI on the Status of the Netlify Deploy Part I</title>
      <link>https://haseebmajid.dev/posts/2023-01-17-how-can-we-update-gitlab-ci-on-the-status-of-the-netlify-deploy-part-i/</link>
      <pubDate>Tue, 17 Jan 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-01-17-how-can-we-update-gitlab-ci-on-the-status-of-the-netlify-deploy-part-i/</guid>
      <description>&lt;p&gt;In this post, I will show you how you can update your GitLab CI pipeline with the status of your Netlify deploys.
As it is quite nice to have everything in one place if our CI pipeline passed or failed.&lt;/p&gt;
&lt;p&gt;I have spoken about this in a
&lt;a href=&#34;https://haseebmajid.dev/posts/2022-12-03-my-workflow-to-create-a-new-post-using-hugo,-netlifycms,-netlify-and-gitlab-together&#34;&gt;previous article&lt;/a&gt;,
how you might go about setting up merge request preview environments with Netlify.&lt;/p&gt;
&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2023-01-17-how-can-we-update-gitlab-ci-on-the-status-of-the-netlify-deploy-part-i/images/netlify_mr_comment.png&#34;
        type=&#34;&#34;
        alt=&#34;Netlify MR comment&#34;
        
      /&gt;&lt;/p&gt;
&lt;p&gt;This can be set up to also leave comments as shown above, and notify us if our website deployed successfully. Which will look like so:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this post, I will show you how you can update your GitLab CI pipeline with the status of your Netlify deploys.
As it is quite nice to have everything in one place if our CI pipeline passed or failed.</p>
<p>I have spoken about this in a
<a href="/posts/2022-12-03-my-workflow-to-create-a-new-post-using-hugo,-netlifycms,-netlify-and-gitlab-together">previous article</a>,
how you might go about setting up merge request preview environments with Netlify.</p>
<p><img
        loading="lazy"
        src="/posts/2023-01-17-how-can-we-update-gitlab-ci-on-the-status-of-the-netlify-deploy-part-i/images/netlify_mr_comment.png"
        type=""
        alt="Netlify MR comment"
        
      /></p>
<p>This can be set up to also leave comments as shown above, and notify us if our website deployed successfully. Which will look like so:</p>
<p><img
        loading="lazy"
        src="/posts/2023-01-17-how-can-we-update-gitlab-ci-on-the-status-of-the-netlify-deploy-part-i/images/netlify_notifications.png"
        type=""
        alt="Netlify Notifications"
        
      /></p>
<p>However, there is currently no way to update our CI pipeline/GitLab on the main branch when our app is deployed on Netlify <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.
Netlify does allow us to set up webhooks depending on the different statuses of our builds. So what we need is to set up webhooks
on Netlify, then have that call something say a serverless function which will then call the GitLab to commit status API. When we update
the commit status it looks something like this:</p>
<p><a href="images/main_branch_ci.png">GitLab CI</a></p>
<h2 id="netlify-gitlab-commit-status">Netlify GitLab Commit Status</h2>
<p>I have created this simple script in Golang <a href="https://gitlab.com/hmajid2301/netlify-gitlab-commit-status">here</a>.
Which does this for us.
In my case, I have it deployed as a google cloud function and have set up CI in that project so it automatically deploys
when we push to the main branch.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">deploy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">google/cloud-sdk:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">gcloud auth activate-service-account --key-file $GCLOUD_SERVICE_KEY</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">gcloud config set project haseeb-majid</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">gcloud functions deploy netlify-build-commit-status --region us-central1 --gen2 --runtime go119 --trigger-http --allow-unauthenticated --entry-point=webhook</span><span class="w">
</span></span></span></code></pre></div><details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">Limitation</summary>
  
  At the moment the script will only work with repositories you are the owner of in GitLab.
Hence you cannot just say point to my google cloud function and get it to work with your repository.
</details>

<h2 id="google-cloud">Google Cloud</h2>
<h3 id="set-up">Set Up</h3>
<p>Assuming you want to deploy this as a cloud function as well, fork my repo.
Then go to <a href="https://console.cloud.google.com/welcome">Google cloud platform</a> (GCP).</p>
<ol>
<li>Create a new project if you need to.</li>
<li>Go to service accounts and create a new service account i.e. I called my <code>functions@[project].iam.gserviceaccount.com</code></li>
<li>Assign the service account at least these two permissions <code>Cloud Functions Developer</code>, <code>Service Account User</code></li>
<li>Go to <code>Secret Manager</code>, then create two secrets
<ol>
<li><code>JWS_SECRET</code>: A secret shared with Netlify webhooks so we can confirm the webhook came from Netlify</li>
<li><code>GITLAB_TOKEN</code>: Used to interact with GitLab API
<ol>
<li>Create on GitLab by clicking on your Avatar (top right)</li>
<li>Select <code>Preferences</code></li>
<li>Select <code>Access Token</code> (left sidebar)</li>
<li>Create a new token with <code>api, read_repository</code> (as shown in the image below)</li>
</ol>
</li>
</ol>
</li>
</ol>
<p>You set permissions on the service account in IAM like so:</p>
<p><a href="images/iam_gcp.png">GCP IAM Permissions</a></p>
<p>You can create a GitLab token like so:</p>
<p><a href="images/gitlab_access_token.png">GitLab Access Token</a></p>
<h3 id="cloud-function">Cloud Function</h3>
<p>I think the easiest way to deploy this function the first time is to do it manually so we can also add our secrets.
Which CI currently does not do for us. Here are the settings I have used:</p>
<p>First, let&rsquo;s create a new function and use gen2 which comes with a bunch of improvements.</p>
<p><img
        loading="lazy"
        src="/posts/2023-01-17-how-can-we-update-gitlab-ci-on-the-status-of-the-netlify-deploy-part-i/images/function_gen2.png"
        type=""
        alt="Function Gen2"
        
      /></p>
<p>Next, let&rsquo;s set the max instance count to 1, we don&rsquo;t need 100 instances running this code 😂.</p>
<p><img
        loading="lazy"
        src="/posts/2023-01-17-how-can-we-update-gitlab-ci-on-the-status-of-the-netlify-deploy-part-i/images/function_resources.png"
        type=""
        alt="Function Resources"
        
      /></p>
<p>Next, add the secrets we created above as ENV variables, in this example, I have added just <code>GITLAB_TOKEN</code> but we also need <code>JWS_SECRET</code>.
Also with the same name as the ENV variable.</p>
<p><img
        loading="lazy"
        src="/posts/2023-01-17-how-can-we-update-gitlab-ci-on-the-status-of-the-netlify-deploy-part-i/images/function_secrets.png"
        type=""
        alt="Function Secrets"
        
      /></p>
<p>Finally copy the code from the repo over to this section including <code>go.mod</code> file.</p>
<p></p>
<p>After this, we should be able to deploy using CI from now on. Take note of the URL of the function.</p>
<h3 id="gitlab">GitLab</h3>
<p>We do need to do one final thing to setup GitLab CI to publish our cloud function we will need to get the JSON key file-related
to our service account and put that as a secret CI/CD variable in GitLab. To do this:</p>
<ul>
<li>Go to your service account</li>
<li>Go to <code>keys</code> and select <code>Add Key</code> &gt; <code>Create new key</code></li>
<li>Select <code>JSON</code></li>
<li>Copy the contents of the files and go to GitLab CI/CD settings for your project
<ul>
<li>Call this variable <code>GCLOUD_SERVICE_KEY</code></li>
<li>When creating a new variable specify the type as <code>file</code> (see image below)</li>
</ul>
</li>
</ul>
<p>GCP service account key export:</p>
<p><img
        loading="lazy"
        src="/posts/2023-01-17-how-can-we-update-gitlab-ci-on-the-status-of-the-netlify-deploy-part-i/images/gcp_keys.png"
        type=""
        alt="GCP Service Keys"
        
      /></p>
<p>CI/CD variables for repo in GitLab:</p>
<p><a href="images/gitlab_ci_vars.png">GitLab CI</a></p>
<p>Create a new variable:</p>
<p><a href="images/gitlab_ci_new_var.png">GitLab CI</a></p>
<h2 id="netlify">Netlify</h2>
<p>Now that we deployed our function let&rsquo;s go to Netlify, go to your site settings on Netlify.</p>
<ul>
<li>Then go to <code>Build &amp; Deploy</code> (left-hand side)</li>
<li>Go to <code>Deploy notifications</code></li>
<li><code>Add notifications</code> &gt; <code>Outgoing webhook</code>
<ul>
<li>Select <code>Deploy Started</code></li>
<li>Paste the URL of the cloud function and the JWS secret</li>
</ul>
</li>
</ul>
<p>Now do this also for <code>Deploy failed</code> and `Deploy succeeded</p>
<p><img
        loading="lazy"
        src="/posts/2023-01-17-how-can-we-update-gitlab-ci-on-the-status-of-the-netlify-deploy-part-i/images/netlify_deploy.png"
        type=""
        alt="Outgoing webhook"
        
      /></p>
<p>Now Netlify will call out the cloud function and depending on the Netlify status it will set the relevant commit status
on our GitLab repo/CI.</p>
<p>In part II we will go over how the code works! But for now, you should be able to get &ldquo;deploy&rdquo; statuses in GitLab CI.
Which is nice. It will be called <code>Deploy to Netlify</code>, this is set within the code itself.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/netlify-gitlab-commit-status">Netlify GitLab Commit Status Repo</a></li>
</ul>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://answers.netlify.com/t/gitlab-commit-status/79572">https://answers.netlify.com/t/gitlab-commit-status/79572</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How Includes, Extends work with TS Config (with SvelteKit)</title>
      <link>https://haseebmajid.dev/posts/2023-01-14-til-how-includes-extends-work-with-ts-config-with-sveltekit/</link>
      <pubDate>Sat, 14 Jan 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-01-14-til-how-includes-extends-work-with-ts-config-with-sveltekit/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How Includes, Extends work with TS Config (with SvelteKit)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I have recently been creating an app with SvelteKit and Typescript. I noticed all of a sudden Typescript and VS Code not playing
nice with each other. It wouldn&amp;rsquo;t show me the types of variables that I knew it was showing me before. So I started to investigate
and work out what was wrong. I was getting &lt;code&gt;locals&lt;/code&gt; with a type &lt;code&gt;any&lt;/code&gt;:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How Includes, Extends work with TS Config (with SvelteKit)</strong></p>
<p>I have recently been creating an app with SvelteKit and Typescript. I noticed all of a sudden Typescript and VS Code not playing
nice with each other. It wouldn&rsquo;t show me the types of variables that I knew it was showing me before. So I started to investigate
and work out what was wrong. I was getting <code>locals</code> with a type <code>any</code>:</p>
<p><img
        loading="lazy"
        src="/posts/2023-01-14-til-how-includes-extends-work-with-ts-config-with-sveltekit/images/errors.png"
        type=""
        alt="TS Errors"
        
      /></p>
<p>Even though <code>locals</code> has a defined type in my <code>app.d.ts</code></p>
<p>I used the documentation to create my SvelteKit app like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">declare</span> <span class="kr">namespace</span> <span class="nx">App</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="kr">type</span> <span class="nx">PocketBase</span> <span class="o">=</span> <span class="kr">import</span><span class="p">(</span><span class="s2">&#34;pocketbase&#34;</span><span class="p">).</span><span class="k">default</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kr">interface</span> <span class="nx">Locals</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">user?</span>: <span class="kt">import</span><span class="p">(</span><span class="s2">&#34;pocketbase&#34;</span><span class="p">).</span><span class="nx">Record</span> <span class="o">|</span> <span class="kr">import</span><span class="p">(</span><span class="s2">&#34;pocketbase&#34;</span><span class="p">).</span><span class="nx">Admin</span> <span class="o">|</span> <span class="kc">null</span> <span class="o">|</span> <span class="kc">undefined</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="nx">pb?</span>: <span class="kt">PocketBase</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">npm create svelte@latest my-app
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> my-app
</span></span><span class="line"><span class="cl">npm install
</span></span><span class="line"><span class="cl">npm run dev
</span></span></code></pre></div><p>This gives us a <code>tsconfig.json</code> file that looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&#34;extends&#34;</span><span class="p">:</span> <span class="s2">&#34;./.svelte-kit/tsconfig.json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&#34;compilerOptions&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nt">&#34;strict&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nt">&#34;allowUnreachableCode&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nt">&#34;exactOptionalPropertyTypes&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nt">&#34;noImplicitAny&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nt">&#34;noImplicitOverride&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nt">&#34;noImplicitReturns&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nt">&#34;noImplicitThis&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nt">&#34;noFallthroughCasesInSwitch&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nt">&#34;noUncheckedIndexedAccess&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nt">&#34;types&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;vite-plugin-pwa/client&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>My <code>tsconfig.json</code> looked like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&#34;extends&#34;</span><span class="p">:</span> <span class="s2">&#34;./.svelte-kit/tsconfig.json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="nt">&#34;compilerOptions&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nt">&#34;strict&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nt">&#34;allowUnreachableCode&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nt">&#34;exactOptionalPropertyTypes&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nt">&#34;noImplicitAny&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nt">&#34;noImplicitOverride&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nt">&#34;noImplicitReturns&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nt">&#34;noImplicitThis&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nt">&#34;noFallthroughCasesInSwitch&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nt">&#34;noUncheckedIndexedAccess&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nt">&#34;types&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;vite-plugin-pwa/client&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">	<span class="p">},</span>
</span></span><span class="line hl"><span class="cl">	<span class="nt">&#34;include&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;./setupTest.ts&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The line breaking my configuration was the last <code>include</code> <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> it turns out it was overwriting the include within the <code>./.svelte-kit/tsconfig.json</code>.
That we were extending above. Which had defined its own include:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;include&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;ambient.d.ts&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;./types/**/$types.d.ts&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;../vite.config.ts&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;../src/**/*.js&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;../src/**/*.ts&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;../src/**/*.svelte&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;../src/**/*.js&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;../src/**/*.ts&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;../src/**/*.svelte&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;../tests/**/*.js&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;../tests/**/*.ts&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;../tests/**/*.svelte&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Turns out we were just replacing all these files above with just <code>./setupTest.ts</code>, hence it couldn&rsquo;t find the types that SvelteKit creates
for us <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> 🤦‍♂️.</p>
<p>We were of course now overwriting all these files with just the one we specified. Causing VS Code to throw errors and not type things that
had types. Removing this line <code>&quot;include&quot;: [&quot;./setupTest.ts&quot;]</code> fixed the issue more.
In my case, I just moved this file into my <code>tests</code> folder.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>I was trying to setup this <a href="https://github.com/chaance/vitest-dom">library</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Relevant <a href="https://stackoverflow.com/a/55015988/3108619">SO post</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Autosort our SvelteKit Imports</title>
      <link>https://haseebmajid.dev/posts/2023-01-10-how-to-autosort-our-sveltekit-imports/</link>
      <pubDate>Tue, 10 Jan 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-01-10-how-to-autosort-our-sveltekit-imports/</guid>
      <description>&lt;p&gt;In this post, we will go over how we can auto-sort our imports in our svelte files. To do this we will be using eslint and the
&lt;a href=&#34;https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/order.md&#34;&gt;&lt;code&gt;eslint-import-plugin&lt;/code&gt; plugin&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re anything like me you like have ordered imports, rather than random imports that can be hard to make sense of.
In Python we have &lt;code&gt;isort&lt;/code&gt; in golang we have &lt;code&gt;goimports&lt;/code&gt;. In JavaScript we can use eslint with the above plugin. However,
we need to set up some specific configuration.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this post, we will go over how we can auto-sort our imports in our svelte files. To do this we will be using eslint and the
<a href="https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/order.md"><code>eslint-import-plugin</code> plugin</a>.</p>
<p>If you&rsquo;re anything like me you like have ordered imports, rather than random imports that can be hard to make sense of.
In Python we have <code>isort</code> in golang we have <code>goimports</code>. In JavaScript we can use eslint with the above plugin. However,
we need to set up some specific configuration.</p>
<h2 id="setup">Setup</h2>
<p>I will create a new SvelteKit application but this should be equally applicable to existing applications and should
work with both SvelteKit and Svelte. As all we are going to do is organise our imports.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">npm create svelte@latest example
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> example
</span></span><span class="line"><span class="cl">npm install
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Output</span>
</span></span><span class="line"><span class="cl"><span class="c1"># create-svelte version 2.1.0</span>
</span></span><span class="line"><span class="cl"><span class="c1"># </span>
</span></span><span class="line"><span class="cl"><span class="c1"># Welcome to SvelteKit!</span>
</span></span><span class="line"><span class="cl"><span class="c1"># </span>
</span></span><span class="line"><span class="cl"><span class="c1"># ✔ Which Svelte app template? › SvelteKit demo app</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ✔ Add type checking with TypeScript? › Yes, using TypeScript syntax</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ✔ Add ESLint for code linting? … No / Yes</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ✔ Add Prettier for code formatting? … No / Yes</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ✔ Add Playwright for browser testing? … No / Yes</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ✔ Add Vitest for unit testing? … No / Yes</span>
</span></span></code></pre></div><h2 id="eslint">eslint</h2>
<p>Now onto the main part of this post let us edit our <code>.eslintrc.cjs</code> file which looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">root</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="nx">parser</span><span class="o">:</span> <span class="s1">&#39;@typescript-eslint/parser&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="kr">extends</span><span class="o">:</span> <span class="p">[</span><span class="s1">&#39;eslint:recommended&#39;</span><span class="p">,</span> <span class="s1">&#39;plugin:@typescript-eslint/recommended&#39;</span><span class="p">,</span> <span class="s1">&#39;prettier&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">	<span class="nx">plugins</span><span class="o">:</span> <span class="p">[</span><span class="s1">&#39;svelte3&#39;</span><span class="p">,</span> <span class="s1">&#39;@typescript-eslint&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">	<span class="nx">ignorePatterns</span><span class="o">:</span> <span class="p">[</span><span class="s1">&#39;*.cjs&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">	<span class="nx">overrides</span><span class="o">:</span> <span class="p">[{</span> <span class="nx">files</span><span class="o">:</span> <span class="p">[</span><span class="s1">&#39;*.svelte&#39;</span><span class="p">],</span> <span class="nx">processor</span><span class="o">:</span> <span class="s1">&#39;svelte3/svelte3&#39;</span> <span class="p">}],</span>
</span></span><span class="line"><span class="cl">	<span class="nx">settings</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="s1">&#39;svelte3/typescript&#39;</span><span class="o">:</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;typescript&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">},</span>
</span></span><span class="line"><span class="cl">	<span class="nx">parserOptions</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">sourceType</span><span class="o">:</span> <span class="s1">&#39;module&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nx">ecmaVersion</span><span class="o">:</span> <span class="mi">2020</span>
</span></span><span class="line"><span class="cl">	<span class="p">},</span>
</span></span><span class="line"><span class="cl">	<span class="nx">env</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">browser</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nx">es2017</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nx">node</span><span class="o">:</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><h3 id="eslint-plugin-svelte">eslint-plugin-svelte</h3>
<p>First, we need to use an &ldquo;unofficial&rdquo; plugin for Svelte which also comes with an eslint parser (I think).
See the relevant thread <a href="https://github.com/import-js/eslint-plugin-import/issues/2407#issuecomment-1223394415">here</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">npm install eslint-plugin-svelte --save-dev
</span></span></code></pre></div><p>Then we edit our eslint config file so it looks like this now:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">root</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line hl"><span class="cl">	<span class="nx">parser</span><span class="o">:</span> <span class="s1">&#39;@typescript-eslint/parser&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="nx">parserOptions</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">project</span><span class="o">:</span> <span class="s1">&#39;./tsconfig.json&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nx">extraFileExtensions</span><span class="o">:</span> <span class="p">[</span><span class="s1">&#39;.svelte&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">	<span class="p">},</span>
</span></span><span class="line"><span class="cl">	<span class="kr">extends</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">		<span class="s1">&#39;eslint:recommended&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="s1">&#39;plugin:svelte/recommended&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="s1">&#39;plugin:@typescript-eslint/recommended&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="s1">&#39;prettier&#39;</span>
</span></span><span class="line"><span class="cl">	<span class="p">],</span>
</span></span><span class="line"><span class="cl">	<span class="nx">plugins</span><span class="o">:</span> <span class="p">[</span><span class="s1">&#39;@typescript-eslint&#39;</span><span class="p">],</span>
</span></span><span class="line hl"><span class="cl">	<span class="nx">ignorePatterns</span><span class="o">:</span> <span class="p">[</span><span class="s1">&#39;*.cjs&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">	<span class="nx">overrides</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">		<span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="nx">files</span><span class="o">:</span> <span class="p">[</span><span class="s1">&#39;*.svelte&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">			<span class="nx">parser</span><span class="o">:</span> <span class="s1">&#39;svelte-eslint-parser&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">			<span class="nx">parserOptions</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">				<span class="nx">parser</span><span class="o">:</span> <span class="s1">&#39;@typescript-eslint/parser&#39;</span>
</span></span><span class="line"><span class="cl">			<span class="p">}</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="p">],</span>
</span></span><span class="line"><span class="cl">	<span class="nx">settings</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="s1">&#39;svelte3/typescript&#39;</span><span class="o">:</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;typescript&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">},</span>
</span></span><span class="line"><span class="cl">	<span class="nx">parserOptions</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">sourceType</span><span class="o">:</span> <span class="s1">&#39;module&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nx">ecmaVersion</span><span class="o">:</span> <span class="mi">2020</span>
</span></span><span class="line"><span class="cl">	<span class="p">},</span>
</span></span><span class="line"><span class="cl">	<span class="nx">env</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">browser</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nx">es2017</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nx">node</span><span class="o">:</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><h3 id="eslint-import-plugin">eslint-import-plugin</h3>
<p>Now let&rsquo;s add the plugin that will actually update our imports.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">npm install eslint-plugin-import --save-dev
</span></span></code></pre></div><p>Now we want to edit our eslint config file so it looks like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">root</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="nx">parser</span><span class="o">:</span> <span class="s1">&#39;@typescript-eslint/parser&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="nx">parserOptions</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">project</span><span class="o">:</span> <span class="s1">&#39;./tsconfig.json&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nx">extraFileExtensions</span><span class="o">:</span> <span class="p">[</span><span class="s1">&#39;.svelte&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">	<span class="p">},</span>
</span></span><span class="line"><span class="cl">	<span class="kr">extends</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">		<span class="s1">&#39;eslint:recommended&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="s1">&#39;plugin:svelte/recommended&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="s1">&#39;plugin:@typescript-eslint/recommended&#39;</span><span class="p">,</span>
</span></span><span class="line hl"><span class="cl">		<span class="s1">&#39;prettier&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;plugin:import/errors&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="s1">&#39;plugin:import/warnings&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;plugin:import/typescript&#39;</span>
</span></span><span class="line"><span class="cl">	<span class="p">],</span>
</span></span><span class="line"><span class="cl">	<span class="nx">plugins</span><span class="o">:</span> <span class="p">[</span><span class="s1">&#39;@typescript-eslint&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">	<span class="nx">ignorePatterns</span><span class="o">:</span> <span class="p">[</span><span class="s1">&#39;*.cjs&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">	<span class="nx">overrides</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">		<span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="nx">files</span><span class="o">:</span> <span class="p">[</span><span class="s1">&#39;*.svelte&#39;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">			<span class="nx">parser</span><span class="o">:</span> <span class="s1">&#39;svelte-eslint-parser&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">			<span class="nx">parserOptions</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">				<span class="nx">parser</span><span class="o">:</span> <span class="s1">&#39;@typescript-eslint/parser&#39;</span>
</span></span><span class="line"><span class="cl">			<span class="p">}</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="p">],</span>
</span></span><span class="line hl"><span class="cl">	<span class="nx">rules</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="s1">&#39;import/order&#39;</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">			<span class="s1">&#39;warn&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">			<span class="p">{</span>
</span></span><span class="line"><span class="cl">				<span class="nx">alphabetize</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">					<span class="nx">order</span><span class="o">:</span> <span class="s1">&#39;asc&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">					<span class="nx">caseInsensitive</span><span class="o">:</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">				<span class="p">},</span>
</span></span><span class="line"><span class="cl">				<span class="s1">&#39;newlines-between&#39;</span><span class="o">:</span> <span class="s1">&#39;always&#39;</span>
</span></span><span class="line"><span class="cl">			<span class="p">}</span>
</span></span><span class="line"><span class="cl">		<span class="p">]</span>
</span></span><span class="line"><span class="cl">	<span class="p">},</span>
</span></span><span class="line"><span class="cl">	<span class="nx">settings</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="s1">&#39;svelte3/typescript&#39;</span><span class="o">:</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;typescript&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">},</span>
</span></span><span class="line"><span class="cl">	<span class="nx">parserOptions</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">sourceType</span><span class="o">:</span> <span class="s1">&#39;module&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nx">ecmaVersion</span><span class="o">:</span> <span class="mi">2020</span>
</span></span><span class="line"><span class="cl">	<span class="p">},</span>
</span></span><span class="line"><span class="cl">	<span class="nx">env</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">browser</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nx">es2017</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nx">node</span><span class="o">:</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><h4 id="importorder">import/order</h4>
<p>The most interesting change we did is to add a bunch of rules. We want to add rules for import/order which is what is used to sort our imports.
In the example below we are setting any incorrectly ordered imports to warn us rather than throw an error. Then we are telling it to sort
our imports alphabetically. We are also setting it to create new lines between the various groups. Where a group would be something like:
<code>builtin</code>, <code>external</code>, <code>parent</code>, you can read more about it <a href="https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/order.md#importorder">here</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">rules</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;import/order&#39;</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;warn&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">alphabetize</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nx">order</span><span class="o">:</span> <span class="s1">&#39;asc&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="nx">caseInsensitive</span><span class="o">:</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">            <span class="p">},</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;newlines-between&#39;</span><span class="o">:</span> <span class="s1">&#39;always&#39;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">},</span>
</span></span></code></pre></div><p>We can auto fix imports if we run <code>eslint</code> with the <code>--fix</code> argument i.e. <code>eslint --fix .</code>. We can also our IDE auto sort our imports on save
in VS Code add the following option to your <code>settings.json</code> file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;eslint.codeActionsOnSave.mode&#34;</span><span class="p">:</span> <span class="s2">&#34;all&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This will attempt to fix all the issues raised by eslint for our file when we save it (you will need the eslint extension, <a href="https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)">https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)</a>.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2023-01-10-how-to-autosort-our-sveltekit-imports/source_code">Example source code</a></li>
<li><a href="https://gitlab.com/bookmarkey/gui/-/commit/db184d8ddd427e81d8884e65c6a5f013bb30ab2c">Example commit adding import order</a></li>
<li><a href="https://gitlab.com/bookmarkey/gui/-/blob/886f230e0c6c75b6f5f7f9e445205fd90b6fbf33/.eslintrc.cjs">Example eslint confif file</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to add Have I Been Pwned to Your Registration Flow in SvelteKit</title>
      <link>https://haseebmajid.dev/posts/2023-01-08-how-to-add-have-i-been-pwned-to-your-registration-flow-in-sveltekit/</link>
      <pubDate>Sun, 08 Jan 2023 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2023-01-08-how-to-add-have-i-been-pwned-to-your-registration-flow-in-sveltekit/</guid>
      <description>&lt;p&gt;In this post, I will go over how you can use the have I been pwned (hibp) API to improve UX &lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; in our registration flow in our SvelteKit app.
When the user signs up we can check if that password has been compromised in a previous data breach (on another site).
Then we can prompt the user to enter another password. This will help avoid users using simple &amp;amp; common passwords like &lt;code&gt;password11&lt;/code&gt;.
Ideally, our user would be using a password manager to generate secure and strong passwords. You can see us using the hibp website below,
of course, in our case, we will want to use the API.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this post, I will go over how you can use the have I been pwned (hibp) API to improve UX <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> in our registration flow in our SvelteKit app.
When the user signs up we can check if that password has been compromised in a previous data breach (on another site).
Then we can prompt the user to enter another password. This will help avoid users using simple &amp; common passwords like <code>password11</code>.
Ideally, our user would be using a password manager to generate secure and strong passwords. You can see us using the hibp website below,
of course, in our case, we will want to use the API.</p>
<p><img
        loading="lazy"
        src="/posts/2023-01-08-how-to-add-have-i-been-pwned-to-your-registration-flow-in-sveltekit/images/hibp.png"
        type=""
        alt="hibn"
        
      /></p>
<details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">Dependencies</summary>
  
  In this post, we will be using TailwindCSS and Zod [^2]. I will do another post on these in more detail.
</details>

<h2 id="current-application">Current Application</h2>
<h3 id="pagesvelte">+page.svelte</h3>
<p>Let&rsquo;s pretend we have a simple registration form at <code>src/routes/+page.svelte</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-svelte" data-lang="svelte"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">lang</span><span class="o">=</span><span class="s">&#34;ts&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="kr">import</span> <span class="kr">type</span> <span class="p">{</span> <span class="nx">ActionData</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;./$types&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">export</span> <span class="kd">let</span> <span class="nx">form</span>: <span class="kt">ActionData</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;mx-auto flex w-full flex-col items-center justify-center px-6 py-8 md:h-screen lg:py-0&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">div</span>
</span></span><span class="line"><span class="cl">      <span class="na">class</span><span class="o">=</span><span class="s">&#34;flex flex-col rounded-2xl border-slate-100 bg-white dark:bg-slate-900/70 md:w-2/3 lg:w-1/3 w-full&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;space-y-4 p-6 sm:p-8 md:space-y-6&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;space-y-4 p-6 sm:p-8 md:space-y-6&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">h1</span>
</span></span><span class="line"><span class="cl">            <span class="na">class</span><span class="o">=</span><span class="s">&#34;text-xl font-bold leading-tight tracking-tight text-gray-900 dark:text-white md:text-2xl&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            Create an account
</span></span><span class="line"><span class="cl">        <span class="p">&lt;/</span><span class="nt">h1</span><span class="p">&gt;</span>
</span></span><span class="line hl"><span class="cl">        <span class="p">&lt;</span><span class="nt">form</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;space-y-4 md:space-y-6&#34;</span> <span class="na">action</span><span class="o">=</span><span class="s">&#34;?/register&#34;</span> <span class="na">method</span><span class="o">=</span><span class="s">&#34;post&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;mb-6 last:mb-0&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                <span class="p">&lt;</span><span class="nt">label</span> <span class="na">for</span><span class="o">=</span><span class="s">&#34;email&#34;</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;mb-2 block font-bold &#34;</span><span class="p">&gt;</span>Email<span class="p">&lt;/</span><span class="nt">label</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                <span class="p">&lt;</span><span class="nt">input</span>
</span></span><span class="line"><span class="cl">                    <span class="na">id</span><span class="o">=</span><span class="s">&#34;email&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="na">autocomplete</span><span class="o">=</span><span class="s">&#34;username&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="na">name</span><span class="o">=</span><span class="s">&#34;email&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="na">required</span><span class="o">=</span><span class="p">{</span><span class="kc">true</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">                    <span class="s">type=&#34;email&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="na">placeholder</span><span class="o">=</span><span class="s">&#34;your@email.com&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="na">value</span><span class="o">=</span><span class="p">{</span><span class="nx">form</span><span class="o">?</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">email</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">                    <span class="s">class=&#34;h-12</span> <span class="na">w-full</span> <span class="na">max-w-full</span> <span class="na">rounded</span> <span class="na">border</span> <span class="na">bg-white</span> <span class="na">px-3</span> <span class="na">py-2</span> <span class="na">pl-10</span> <span class="na">focus:ring</span> <span class="na">dark:border-gray-700</span> <span class="na">dark:bg-slate-800</span> <span class="na">dark:placeholder-gray-400</span> <span class="na">border-gray-300</span><span class="err">&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;mt-1 py-1 text-xs font-semibold text-gray-500 dark:text-slate-400&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                    Required. Your email
</span></span><span class="line"><span class="cl">                <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;relative mb-6 w-full last:mb-0&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;mb-6 last:mb-0&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">&lt;</span><span class="nt">label</span> <span class="na">for</span><span class="o">=</span><span class="s">&#34;password&#34;</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;mb-2 block font-bold &#34;</span><span class="p">&gt;</span>Password<span class="p">&lt;/</span><span class="nt">label</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                    <span class="p">&lt;</span><span class="nt">input</span>
</span></span><span class="line"><span class="cl">                        <span class="na">id</span><span class="o">=</span><span class="s">&#34;password&#34;</span>
</span></span><span class="line"><span class="cl">                        <span class="na">autocomplete</span><span class="o">=</span><span class="s">&#34;new-password&#34;</span>
</span></span><span class="line"><span class="cl">                        <span class="na">name</span><span class="o">=</span><span class="s">&#34;password&#34;</span>
</span></span><span class="line"><span class="cl">                        <span class="na">required</span><span class="o">=</span><span class="p">{</span><span class="kc">true</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">                        <span class="s">type=&#34;password&#34;</span>
</span></span><span class="line"><span class="cl">                        <span class="na">placeholder</span><span class="o">=</span><span class="s">&#34;Your password&#34;</span>
</span></span><span class="line"><span class="cl">                        <span class="na">value</span><span class="o">=</span><span class="p">{</span><span class="nx">form</span><span class="o">?</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">password</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">                        <span class="s">class=&#34;h-12</span> <span class="na">w-full</span> <span class="na">max-w-full</span> <span class="na">rounded</span> <span class="na">border</span> <span class="na">bg-white</span> <span class="na">px-3</span> <span class="na">py-2</span> <span class="na">pl-10</span> <span class="na">focus:ring</span> <span class="na">dark:border-gray-700</span> <span class="na">dark:bg-slate-800</span> <span class="na">dark:placeholder-gray-400</span> <span class="na">border-gray-300</span><span class="err">&#34;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                    <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;mt-1 py-1 text-xs font-semibold text-gray-500 dark:text-slate-400&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                        Required. Your password
</span></span><span class="line"><span class="cl">                    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">button</span>
</span></span><span class="line"><span class="cl">                <span class="na">class</span><span class="o">=</span><span class="s">&#34;inline-flex cursor-pointer items-center justify-center whitespace-nowrap rounded border border-blue-600 bg-blue-600 px-1 py-1 font-semibold text-white ring-blue-300 transition-colors duration-150 last:mr-0 hover:border-blue-700 hover:bg-blue-700 focus:outline-none focus:ring dark:border-blue-500 dark:bg-blue-500 dark:ring-blue-700 hover:dark:border-blue-600 hover:dark:bg-blue-600 lg:px-2 lg:py-2 my-2 w-full&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="na">type</span><span class="o">=</span><span class="s">&#34;submit&#34;</span><span class="p">&gt;</span>Create Account<span class="p">&lt;/</span><span class="nt">button</span>
</span></span><span class="line"><span class="cl">            <span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;/</span><span class="nt">form</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span><span class="k">#each</span> <span class="nx">form</span><span class="o">?</span><span class="p">.</span><span class="nx">errors</span><span class="o">?</span><span class="p">.</span><span class="nx">email</span> <span class="o">||</span> <span class="p">[]</span> <span class="kr">as</span> <span class="nx">error</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;{</span><span class="nx">error</span><span class="p">}&lt;/</span><span class="nt">p</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span><span class="k">/each</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span><span class="k">#each</span> <span class="nx">form</span><span class="o">?</span><span class="p">.</span><span class="nx">errors</span><span class="o">?</span><span class="p">.</span><span class="nx">password</span> <span class="o">||</span> <span class="p">[]</span> <span class="kr">as</span> <span class="nx">error</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;{</span><span class="nx">error</span><span class="p">}&lt;/</span><span class="nt">p</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span><span class="k">/each</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span></code></pre></div><h3 id="pageserverts">+page.server.ts</h3>
<p>and and let&rsquo;s say we have a <code>src/routes/+page.server.ts</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">fail</span><span class="p">,</span> <span class="nx">redirect</span><span class="p">,</span> <span class="kr">type</span> <span class="nx">Actions</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;@sveltejs/kit&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">pwnedPassword</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;hibp&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">z</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;zod&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">interface</span> <span class="nx">Register</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">email</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">password</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">registerSchema</span>: <span class="kt">z.ZodType</span><span class="p">&lt;</span><span class="nt">Register</span><span class="p">&gt;</span> <span class="o">=</span> <span class="nx">z</span><span class="p">.</span><span class="kt">object</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nx">email</span>: <span class="kt">z</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="kt">string</span><span class="p">({</span> <span class="nx">required_error</span><span class="o">:</span> <span class="s1">&#39;Email is required&#39;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="nx">email</span><span class="p">({</span> <span class="nx">message</span><span class="o">:</span> <span class="s1">&#39;Email must be a valid email.&#39;</span> <span class="p">}),</span>
</span></span><span class="line"><span class="cl">    <span class="nx">password</span>: <span class="kt">z</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="kt">string</span><span class="p">({</span> <span class="nx">required_error</span><span class="o">:</span> <span class="s1">&#39;Password is required&#39;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="nx">min</span><span class="p">(</span><span class="mi">8</span><span class="p">,</span> <span class="s1">&#39;Password must be a minimum of 8 characters.&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="nx">actions</span>: <span class="kt">Actions</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">register</span>: <span class="kt">async</span> <span class="p">({</span> <span class="nx">request</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="kr">const</span> <span class="nx">data</span>: <span class="kt">Register</span> <span class="o">=</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">fromEntries</span><span class="p">((</span><span class="k">await</span> <span class="nx">request</span><span class="p">.</span><span class="nx">formData</span><span class="p">())</span> <span class="kr">as</span> <span class="nx">Iterable</span><span class="o">&lt;</span><span class="p">[</span><span class="nx">Register</span><span class="p">]</span><span class="o">&gt;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="kr">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">registerSchema</span><span class="p">.</span><span class="nx">safeParseAsync</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">result</span><span class="p">.</span><span class="nx">success</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="k">return</span> <span class="nx">fail</span><span class="p">(</span><span class="mi">400</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">              <span class="nx">data</span>: <span class="kt">data</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="nx">errors</span>: <span class="kt">result.error.flatten</span><span class="p">().</span><span class="nx">fieldErrors</span>
</span></span><span class="line"><span class="cl">          <span class="p">});</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1">// Create user logic here ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>      <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;CREATE USER&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="k">throw</span> <span class="nx">redirect</span><span class="p">(</span><span class="mi">303</span><span class="p">,</span> <span class="s1">&#39;/&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>Here we are using Zod to do our form validation for us <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>. Zod is a great typescript first schema validation library <sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>.
In this case, we have some simple validation rules:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">interface</span> <span class="nx">Register</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">email</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">password</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">registerSchema</span>: <span class="kt">z.ZodType</span><span class="p">&lt;</span><span class="nt">Register</span><span class="p">&gt;</span> <span class="o">=</span> <span class="nx">z</span><span class="p">.</span><span class="kt">object</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nx">email</span>: <span class="kt">z</span>
</span></span><span class="line"><span class="cl">      <span class="p">.</span><span class="kt">string</span><span class="p">({</span> <span class="nx">required_error</span><span class="o">:</span> <span class="s1">&#39;Email is required&#39;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl">      <span class="p">.</span><span class="nx">email</span><span class="p">({</span> <span class="nx">message</span><span class="o">:</span> <span class="s1">&#39;Email must be a valid email.&#39;</span> <span class="p">}),</span>
</span></span><span class="line"><span class="cl">  <span class="nx">password</span>: <span class="kt">z</span>
</span></span><span class="line"><span class="cl">      <span class="p">.</span><span class="kt">string</span><span class="p">({</span> <span class="nx">required_error</span><span class="o">:</span> <span class="s1">&#39;Password is required&#39;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl">      <span class="p">.</span><span class="nx">min</span><span class="p">(</span><span class="mi">8</span><span class="p">,</span> <span class="s1">&#39;Password must be a minimum of 8 characters.&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><ul>
<li>Email: check it is a valid email and is not empty</li>
<li>Password: check it is at least 8 characters and is not empty.</li>
</ul>
<p>The next part of the file is our form action <sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>, which receives a request from our form in the <code>+page.svelte</code> file (see above highlighted line).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="nx">actions</span>: <span class="kt">Actions</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">register</span>: <span class="kt">async</span> <span class="p">({</span> <span class="nx">request</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">data</span>: <span class="kt">Register</span> <span class="o">=</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">fromEntries</span><span class="p">((</span><span class="k">await</span> <span class="nx">request</span><span class="p">.</span><span class="nx">formData</span><span class="p">())</span> <span class="kr">as</span> <span class="nx">Iterable</span><span class="o">&lt;</span><span class="p">[</span><span class="nx">Register</span><span class="p">]</span><span class="o">&gt;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">registerSchema</span><span class="p">.</span><span class="nx">safeParseAsync</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">result</span><span class="p">.</span><span class="nx">success</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nx">fail</span><span class="p">(</span><span class="mi">400</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">data</span>: <span class="kt">data</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nx">errors</span>: <span class="kt">result.error.flatten</span><span class="p">().</span><span class="nx">fieldErrors</span>
</span></span><span class="line"><span class="cl">        <span class="p">});</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Create user logic here ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;CREATE USER&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">throw</span> <span class="nx">redirect</span><span class="p">(</span><span class="mi">303</span><span class="p">,</span> <span class="s1">&#39;/&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><details
  class="notice tip"
  open="true"
>
    <summary class="notice-title">POST Request</summary>
  
  What is happening is the form is sending a POST request to <code>/register</code>, which matches our actions name.
Then the code in this action function is called and we can do what we want.
</details>

<p>The first part of the function just checks if the data passed from the form is valid using Zod.
If it fails validation it returns errors, which we can then show the user.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">data</span>: <span class="kt">Register</span> <span class="o">=</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">fromEntries</span><span class="p">((</span><span class="k">await</span> <span class="nx">request</span><span class="p">.</span><span class="nx">formData</span><span class="p">())</span> <span class="kr">as</span> <span class="nx">Iterable</span><span class="o">&lt;</span><span class="p">[</span><span class="nx">Register</span><span class="p">]</span><span class="o">&gt;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">registerSchema</span><span class="p">.</span><span class="nx">safeParseAsync</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">result</span><span class="p">.</span><span class="nx">success</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="nx">fail</span><span class="p">(</span><span class="mi">400</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">data</span>: <span class="kt">data</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">errors</span>: <span class="kt">result.error.flatten</span><span class="p">().</span><span class="nx">fieldErrors</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Which we do in <code>+page.svelte</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-svelte" data-lang="svelte"><span class="line"><span class="cl"><span class="p">{</span><span class="k">#each</span> <span class="nx">form</span><span class="o">?</span><span class="p">.</span><span class="nx">errors</span><span class="o">?</span><span class="p">.</span><span class="nx">email</span> <span class="o">||</span> <span class="p">[]</span> <span class="kr">as</span> <span class="nx">error</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;{</span><span class="nx">error</span><span class="p">}&lt;/</span><span class="nt">p</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span><span class="k">/each</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span><span class="k">#each</span> <span class="nx">form</span><span class="o">?</span><span class="p">.</span><span class="nx">errors</span><span class="o">?</span><span class="p">.</span><span class="nx">password</span> <span class="o">||</span> <span class="p">[]</span> <span class="kr">as</span> <span class="nx">error</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;{</span><span class="nx">error</span><span class="p">}&lt;/</span><span class="nt">p</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span><span class="k">/each</span><span class="p">}</span>
</span></span></code></pre></div><p>Now that we have a working registration form, how can we add a check if a user&rsquo;s email was compromised?</p>
<h2 id="have-i-been-pwned">Have I Been Pwned</h2>
<p>First, we want to use the hibp API, lucky for us we can use a library that someone has already written.
Handily called <a href="https://github.com/wKovacs64/hibp/"><code>hibp</code></a>. We can install like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">npm i -D hibp
</span></span></code></pre></div><p>Then we need to update our <code>+page.server.ts</code> file to add a function which will return a boolean based
on if the password has been compromised or not.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">isSafePassword</span> <span class="o">=</span> <span class="kr">async</span> <span class="p">(</span><span class="nx">password</span>: <span class="kt">string</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="kr">const</span> <span class="nx">pwned</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">pwnedPassword</span><span class="p">(</span><span class="nx">password</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="k">return</span> <span class="nx">pwned</span> <span class="o">&lt;=</span> <span class="mi">3</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">err</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>Where <code>pwnedPassword</code> returns a promise which resolves to the number of times the password has been exposed in a breach.
In our example above the function returns false if the password has been exposed in 3 or more breaches.</p>
<p>Now in our schema validation let&rsquo;s add the <code>refine</code> function:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">registerSchema</span>: <span class="kt">z.ZodType</span><span class="p">&lt;</span><span class="nt">Register</span><span class="p">&gt;</span> <span class="o">=</span> <span class="nx">z</span><span class="p">.</span><span class="kt">object</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nx">email</span>: <span class="kt">z</span>
</span></span><span class="line"><span class="cl">      <span class="p">.</span><span class="kt">string</span><span class="p">({</span> <span class="nx">required_error</span><span class="o">:</span> <span class="s1">&#39;Email is required&#39;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl">      <span class="p">.</span><span class="nx">email</span><span class="p">({</span> <span class="nx">message</span><span class="o">:</span> <span class="s1">&#39;Email must be a valid email.&#39;</span> <span class="p">}),</span>
</span></span><span class="line"><span class="cl">  <span class="nx">password</span>: <span class="kt">z</span>
</span></span><span class="line"><span class="cl">      <span class="p">.</span><span class="kt">string</span><span class="p">({</span> <span class="nx">required_error</span><span class="o">:</span> <span class="s1">&#39;Password is required&#39;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl">      <span class="p">.</span><span class="nx">min</span><span class="p">(</span><span class="mi">8</span><span class="p">,</span> <span class="s1">&#39;Password must be a minimum of 8 characters.&#39;</span><span class="p">)</span>
</span></span><span class="line hl"><span class="cl">      <span class="p">.</span><span class="nx">refine</span><span class="p">(</span><span class="nx">isSafePassword</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">({</span>
</span></span><span class="line"><span class="cl">          <span class="nx">message</span><span class="o">:</span> <span class="sb">`Password has been compromised, please try again`</span>
</span></span><span class="line"><span class="cl">      <span class="p">}))</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><p>Now the <code>isSafePassword</code> function is used in our schema validation, where if the function returns false
the validation fails and returns the error with the message we specified.</p>
<h2 id="thats-it">That&rsquo;s It!</h2>
<p>That&rsquo;s it, we&rsquo;ve added a simple check to see if the password a user tries to register with has been
previously compromised and also show this error back to the user.</p>
<p>Some other things to look into are progressive enhancement to improve UX <sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup>.</p>
<p><img
        loading="lazy"
        src="/posts/2023-01-08-how-to-add-have-i-been-pwned-to-your-registration-flow-in-sveltekit/images/example.png"
        type=""
        alt="Example"
        
      /></p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2023-01-08-how-to-add-have-i-been-pwned-to-your-registration-flow-in-sveltekit/source_code">Example source code</a></li>
<li><a href="https://gitlab.com/bookmarkey/gui/-/tree/886f230e0c6c75b6f5f7f9e445205fd90b6fbf33/src/routes/(unprotected)/(account)/register">Example Registration Form</a></li>
</ul>
<hr>
<h2 id="image-imagescoverpng">title: {? {slicestr (replace .Name &ldquo;-&rdquo; &quot; &ldquo;) 11 | title: &lsquo;&rsquo;} : &lsquo;&rsquo;}
date: {? {dateFormat &ldquo;2006-01-02&rdquo; .Date: &lsquo;&rsquo;} : &lsquo;&rsquo;}
canonicalURL: <a href="https://haseebmajid.dev/posts/%7B%7B.Name%7D%7D">https://haseebmajid.dev/posts/{{.Name}}</a>
tags: []
cover:
image: images/cover.png</h2>
<h2 id="image-imagescoverpng-1">cover:
image: images/cover.png</h2>
<h2 id="image-imagescoverpng-2">title: Hello World
date: &ldquo;2015-05-01T22:12:03.284Z&rdquo;
description: &ldquo;Hello World&rdquo;
cover:
image: images/cover.png</h2>
<h2 id="this-is-my-first-post-on-my-new-fake-blog-how-exciting-im-sure-ill-write-a-lot-more-interesting-things-in-the-future-oh-and-heres-a-great-quote-from-this-wikipedia-on-salted-duck-eggshttpsenwikipediaorgwikisalted_duck_egg--a-salted-duck-egg-is-a-chinese-preserved-food-product-made-by-soaking-duck--eggs-in-brine-or-packing-each-egg-in-damp-salted-charcoal-in-asian--supermarkets-these-eggs-are-sometimes-sold-covered-in-a-thick-layer-of-salted--charcoal-paste-the-eggs-may-also-be-sold-with-the-salted-paste-removed--wrapped-in-plastic-and-vacuum-packed-from-the-salt-curing-process-the--salted-duck-eggs-have-a-briny-aroma-a-gelatin-like-egg-white-and-a--firm-textured-round-yolk-that-is-bright-orange-red-in-color-chinese-salty-eggsalty_eggjpg">This is my first post on my new fake blog! How exciting! I&rsquo;m sure I&rsquo;ll write a lot more interesting things in the future. Oh, and here&rsquo;s a great quote from this Wikipedia on <a href="https://en.wikipedia.org/wiki/Salted_duck_egg">salted duck eggs</a>. &gt; A salted duck egg is a Chinese preserved food product made by soaking duck &gt; eggs in brine, or packing each egg in damp, salted charcoal. In Asian &gt; supermarkets, these eggs are sometimes sold covered in a thick layer of salted &gt; charcoal paste. The eggs may also be sold with the salted paste removed, &gt; wrapped in plastic, and vacuum packed. From the salt curing process, the &gt; salted duck eggs have a briny aroma, a gelatin-like egg white and a &gt; firm-textured, round yolk that is bright orange-red in color. </h2>
<h2 id="image-imagescoverpng-3">title: My Second Post!
date: &ldquo;2015-05-06&rdquo;
tags: [&ldquo;food&rdquo;, &ldquo;blog&rdquo;]
cover:
image: images/cover.png</h2>
<h2 id="wow-i-love-blogging-so-much-already-did-you-know-that-despite-its-name-salted-duck-eggs-can-also-be-made-from-chicken-eggs-though-the-taste-and-texture-will-be-somewhat-different-and-the-egg-yolk-will-be-less-rich-wikipedia-linkhttpsenwikipediaorgwikisalted_duck_egg-yeah-i-didnt-either">Wow! I love blogging so much already. Did you know that &ldquo;despite its name, salted duck eggs can also be made from chicken eggs, though the taste and texture will be somewhat different, and the egg yolk will be less rich.&rdquo;? (<a href="https://en.wikipedia.org/wiki/Salted_duck_egg">Wikipedia Link</a>) Yeah, I didn&rsquo;t either.</h2>
<h2 id="image-imagescoverpng-4">title: Hello World
date: &ldquo;2015-05-01&rdquo;
tags: [&ldquo;food&rdquo;, &ldquo;duck&rdquo;]
cover:
image: images/cover.png</h2>
<h2 id="this-is-my-first-post-on-my-new-fake-blog-how-exciting-im-sure-ill-write-a-lot-more-interesting-things-in-the-future-oh-and-heres-a-great-quote-from-this-wikipedia-on-salted-duck-eggshttpsenwikipediaorgwikisalted_duck_egg--a-salted-duck-egg-is-a-chinese-preserved-food-product-made-by-soaking-duck--eggs-in-brine-or-packing-each-egg-in-damp-salted-charcoal-in-asian--supermarkets-these-eggs-are-sometimes-sold-covered-in-a-thick-layer-of-salted--charcoal-paste-the-eggs-may-also-be-sold-with-the-salted-paste-removed--wrapped-in-plastic-and-vacuum-packed-from-the-salt-curing-process-the--salted-duck-eggs-have-a-briny-aroma-a-gelatin-like-egg-white-and-a--firm-textured-round-yolk-that-is-bright-orange-red-in-color-chinese-salty-eggsalty_eggjpg-1">This is my first post on my new fake blog! How exciting! I&rsquo;m sure I&rsquo;ll write a lot more interesting things in the future. Oh, and here&rsquo;s a great quote from this Wikipedia on <a href="https://en.wikipedia.org/wiki/Salted_duck_egg">salted duck eggs</a>. &gt; A salted duck egg is a Chinese preserved food product made by soaking duck &gt; eggs in brine, or packing each egg in damp, salted charcoal. In Asian &gt; supermarkets, these eggs are sometimes sold covered in a thick layer of salted &gt; charcoal paste. The eggs may also be sold with the salted paste removed, &gt; wrapped in plastic, and vacuum packed. From the salt curing process, the &gt; salted duck eggs have a briny aroma, a gelatin-like egg white and a &gt; firm-textured, round yolk that is bright orange-red in color. </h2>
<h2 id="image-imagescoverpng-5">title: My Second Post!
date: &ldquo;2015-05-06T23:46:37.121Z&rdquo;
cover:
image: images/cover.png</h2>
<h2 id="wow-i-love-blogging-so-much-already-did-you-know-that-despite-its-name-salted-duck-eggs-can-also-be-made-from-chicken-eggs-though-the-taste-and-texture-will-be-somewhat-different-and-the-egg-yolk-will-be-less-rich-wikipedia-linkhttpsenwikipediaorgwikisalted_duck_egg-yeah-i-didnt-either-1">Wow! I love blogging so much already. Did you know that &ldquo;despite its name, salted duck eggs can also be made from chicken eggs, though the taste and texture will be somewhat different, and the egg yolk will be less rich.&rdquo;? (<a href="https://en.wikipedia.org/wiki/Salted_duck_egg">Wikipedia Link</a>) Yeah, I didn&rsquo;t either.</h2>
<h2 id="image-imagescoverpng-6">title: My Second Post!
date: &ldquo;2015-05-06T23:46:37.121Z&rdquo;
cover:
image: images/cover.png</h2>
<p>Wow! I love blogging so much already. Did you know that &ldquo;despite its name, salted duck eggs can also be made from chicken eggs, though the taste and texture will be somewhat different, and the egg yolk will be less rich.&rdquo;? (<a href="https://en.wikipedia.org/wiki/Salted_duck_egg">Wikipedia Link</a>) Yeah, I didn&rsquo;t either.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://web.dev/sign-up-form-best-practices/">https://web.dev/sign-up-form-best-practices/</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Great tutorial about SvelteKit and Zod <a href="https://www.youtube.com/watch?v=3PYdcm-HBiw">https://www.youtube.com/watch?v=3PYdcm-HBiw</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>Zod, <a href="https://github.com/colinhacks/zod">https://github.com/colinhacks/zod</a>&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p><a href="https://kit.svelte.dev/docs/form-actions">https://kit.svelte.dev/docs/form-actions</a>&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p><a href="https://www.youtube.com/watch?v=jXtzWMhdI2U">https://www.youtube.com/watch?v=jXtzWMhdI2U</a>&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Remote Debug your SvelteKit on an Android Device in Firefox</title>
      <link>https://haseebmajid.dev/posts/2022-12-30-how-to-remote-debug-your-sveltekit-on-an-android-device-in-firefox/</link>
      <pubDate>Fri, 30 Dec 2022 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2022-12-30-how-to-remote-debug-your-sveltekit-on-an-android-device-in-firefox/</guid>
      <description>&lt;p&gt;In this post, we will go over how you can test your SvelteKit app on your Android device in Firefox.
If you&amp;rsquo;re like me you are building an app that you want users to use on both normal PCs (laptops, Desktop) and
their smartphones.&lt;/p&gt;
&lt;p&gt;But the question arises how can we debug our app on a smartphone?
One way to achieve this is to use Firefox and remote debugging, you will need to connect your
smartphone via USB to your device running the SvelteKit app i.e. your Laptop.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this post, we will go over how you can test your SvelteKit app on your Android device in Firefox.
If you&rsquo;re like me you are building an app that you want users to use on both normal PCs (laptops, Desktop) and
their smartphones.</p>
<p>But the question arises how can we debug our app on a smartphone?
One way to achieve this is to use Firefox and remote debugging, you will need to connect your
smartphone via USB to your device running the SvelteKit app i.e. your Laptop.</p>
<h2 id="smartphone">SmartPhone</h2>
<p>On your phone make sure you have developer mode on and have switched on USB debugging <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.
Open up Firefox, then click on the 3 dots to open up the context menu go to <code>Settings</code>,
scroll down and check <code>Remote debugging via USB</code> to &ldquo;on&rdquo;.</p>
<p><img
        loading="lazy"
        src="/posts/2022-12-30-how-to-remote-debug-your-sveltekit-on-an-android-device-in-firefox/images/firefox_usb_debugging.jpeg"
        type=""
        alt="Firefox USB Debugging"
        
      /></p>
<p>Next, connect your smartphone to your PC via USB.</p>
<h2 id="pc">PC</h2>
<p>On your PC start up your SvelteKit application for example <code>npm run dev -- --host --port 5173 --open</code>.
Where <code>dev</code> is <code>vite dev</code> in my <code>package.json</code>. This will start the app but <code>--host</code> will mean it is accessible
to devices on the same private network:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">&gt; bookmarkey-gui@0.0.1 dev
</span></span><span class="line"><span class="cl">&gt; vite dev --host --port <span class="m">5173</span> --open
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  VITE v4.0.1  ready in <span class="m">701</span> ms
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  ➜  Local:   http://localhost:5173/
</span></span><span class="line"><span class="cl">  ➜  Network: http://192.168.0.26:5173/
</span></span><span class="line"><span class="cl">  ➜  Network: http://10.121.2.243:5173/
</span></span><span class="line"><span class="cl">  ➜  Network: http://172.24.0.1:5173/
</span></span><span class="line"><span class="cl">  ➜  press h to show <span class="nb">help</span>
</span></span></code></pre></div><p>This means we can connect to our PC on <code>http://192.168.0.26:5173/</code> from our smartphone and access the SvelteKit site.
You may want to set up your firewall such that only your smartphone can connect to your PC. In my case, since I am only
developing at home I have allowed all traffic on port 5173.</p>
<p><img
        loading="lazy"
        src="/posts/2022-12-30-how-to-remote-debug-your-sveltekit-on-an-android-device-in-firefox/images/ufw_rules.png"
        type=""
        alt="Firewall Rules"
        
      /></p>
<details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">Firewall</summary>
  
  Make sure you know what you are doing when playing around with your firewall rules!
You don&rsquo;t want to allow more access than is necessary. Especially if you working in say
a coffee shop on public WIFI.
</details>

<h3 id="firefox">Firefox</h3>
<p></p>
<p>Now on your PC still:</p>
<ul>
<li>Open Firefox</li>
<li>Go to <code>about:debugging</code></li>
<li>You should see your device under <code>USB enabled</code>.</li>
<li>Select connect</li>
<li>Click on your phone</li>
<li>Then select your SvelteKit app to inspect</li>
</ul>
<p>
<img
        loading="lazy"
        src="/posts/2022-12-30-how-to-remote-debug-your-sveltekit-on-an-android-device-in-firefox/images/firefox_inspect.png"
        type=""
        alt="Firefox Inspect"
        
      />
<img
        loading="lazy"
        src="/posts/2022-12-30-how-to-remote-debug-your-sveltekit-on-an-android-device-in-firefox/images/firefox_console.png"
        type=""
        alt="Firefox Console"
        
      /></p>
<details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">Cannot See Smartphone</summary>
  
  If you don&rsquo;t see your device it may be because your USB preferences are set to only charge your device.
On my phone, I have to change it to <code>File Transfer/Android Auto</code>. Then Firefox can see the phone.
</details>

<p></p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>How to setup USB debugging, <a href="https://www.youtube.com/watch?v=nYoH5B2GXXY">https://www.youtube.com/watch?v=nYoH5B2GXXY</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Setup Netlify, Gitlab And Sentry</title>
      <link>https://haseebmajid.dev/posts/2022-12-29-til-how-to-setup-netlify-gitlab-and-sentry/</link>
      <pubDate>Thu, 29 Dec 2022 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2022-12-29-til-how-to-setup-netlify-gitlab-and-sentry/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Setup Netlify, Gitlab And Sentry&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I will show you in this post how you can integrate Gitlab, Netlify and Sentry. I may do a more
detailed post in the future.&lt;/p&gt;
&lt;h2 id=&#34;gitlab---sentry&#34;&gt;GitLab -&amp;gt; Sentry&lt;/h2&gt;
&lt;p&gt;To connect Gitlab and Sentry follow &lt;a href=&#34;https://docs.sentry.io/product/integrations/source-code-mgmt/gitlab/&#34;&gt;this guide&lt;/a&gt;.
Then add the repos you want to monitor in Sentry. In my case, it is the Bookmarkey GUI.&lt;/p&gt;
&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2022-12-29-til-how-to-setup-netlify-gitlab-and-sentry/images/sentry_repository.png&#34;
        type=&#34;&#34;
        alt=&#34;Sentry Repo List&#34;
        
      /&gt;&lt;/p&gt;
&lt;h2 id=&#34;netlify---gitlab&#34;&gt;Netlify -&amp;gt; GitLab&lt;/h2&gt;
&lt;p&gt;This integration is pretty simple just go through the normal Netlify setup process to add a new site.
You can read more about that &lt;a href=&#34;https://haseebmajid.dev/posts/2022-12-03-my-workflow-to-create-a-new-post-using-hugo,-netlifycms,-netlify-and-gitlab-together&#34;&gt;here&lt;/a&gt;
and &lt;a href=&#34;https://docs.netlify.com/welcome/add-new-site/&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Setup Netlify, Gitlab And Sentry</strong></p>
<p>I will show you in this post how you can integrate Gitlab, Netlify and Sentry. I may do a more
detailed post in the future.</p>
<h2 id="gitlab---sentry">GitLab -&gt; Sentry</h2>
<p>To connect Gitlab and Sentry follow <a href="https://docs.sentry.io/product/integrations/source-code-mgmt/gitlab/">this guide</a>.
Then add the repos you want to monitor in Sentry. In my case, it is the Bookmarkey GUI.</p>
<p><img
        loading="lazy"
        src="/posts/2022-12-29-til-how-to-setup-netlify-gitlab-and-sentry/images/sentry_repository.png"
        type=""
        alt="Sentry Repo List"
        
      /></p>
<h2 id="netlify---gitlab">Netlify -&gt; GitLab</h2>
<p>This integration is pretty simple just go through the normal Netlify setup process to add a new site.
You can read more about that <a href="/posts/2022-12-03-my-workflow-to-create-a-new-post-using-hugo,-netlifycms,-netlify-and-gitlab-together">here</a>
and <a href="https://docs.netlify.com/welcome/add-new-site/">here</a>.</p>
<h2 id="netlify---sentry">Netlify -&gt; Sentry</h2>
<p>Finally, how can we connect Netlify and Sentry?</p>
<p>First, why do we want to do this? It is so we can link specific releases with Netlify deploys.
Such as if it was a deploy preview or a deploy to production.</p>
<p><img
        loading="lazy"
        src="/posts/2022-12-29-til-how-to-setup-netlify-gitlab-and-sentry/images/sentry_releases.png"
        type=""
        alt="Sentry Releases"
        
      /></p>
<p>To do this we will be using the <code>sentry-netlify-build-plugin</code> <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.
First, let&rsquo;s follow the guide in the <a href="https://github.com/getsentry/sentry-netlify-build-plugin#create-a-sentry-internal-integration">README</a>.</p>
<p>We can install this via the GUI in Netlify however I prefer to keep as much of
my config in code. So we will use a <code>netlify.toml</code> file. Append the following:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[[</span><span class="nx">plugins</span><span class="p">]]</span>
</span></span><span class="line"><span class="cl"><span class="nx">package</span> <span class="p">=</span> <span class="s2">&#34;@sentry/netlify-build-plugin&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">plugins</span><span class="p">.</span><span class="nx">inputs</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">sentryOrg</span> <span class="p">=</span> <span class="s2">&#34;majiy&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">sentryProject</span> <span class="p">=</span> <span class="s2">&#34;bookmarkey&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">sentryRepository</span> <span class="p">=</span> <span class="s2">&#34;HaseebMajid Apps / Bookmarkey / gui&#34;</span>
</span></span></code></pre></div><p>Update the specific sentry-related variables to match your project.
Remember since <code>SENTRY_AUTH_TOKEN</code> is a secret variable, created when we set up the internal integration with Netlify,
you should add that to the Netlify GUI directly.</p>
<p><img
        loading="lazy"
        src="/posts/2022-12-29-til-how-to-setup-netlify-gitlab-and-sentry/images/netlify_env_vars.png"
        type=""
        alt="Netlify ENV VARs"
        
      /></p>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">Netlify Build Env Vars</summary>
  
  All the other sentry variables were pre-populated. I only added <code>SENTRY_AUTH_TOKEN</code> via the Netlify GUI.
</details>

<details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">Sentry Repository Name</summary>
  
  <p>Note the name of the repository is not the Git URL but rather
the name of the project shown in your Sentry integration (shown in the above photo).</p>
<p>You can find this name by going to <code>Settings &gt; Repositories</code>.
Then copy the name of the project i.e. <code>HaseebMajid Apps / Bookmarkey / gui</code>.
It is not the hyperlink (<code>gitlab.com/banter-bus/bookmarkey/gui</code>) [^2].</p>

</details>

<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://github.com/getsentry/sentry-netlify-build-plugin">https://github.com/getsentry/sentry-netlify-build-plugin</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How To Add Custom Syntax Highlighting Rules in VS Code</title>
      <link>https://haseebmajid.dev/posts/2022-12-27-til-how-to-add-custom-syntax-highlighting-rules-in-vs-code/</link>
      <pubDate>Tue, 27 Dec 2022 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2022-12-27-til-how-to-add-custom-syntax-highlighting-rules-in-vs-code/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How To Add Custom Syntax Highlighting Rules in VS Code&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The other day (a few months ago) I was comparing Goland and VS Code for Golang development.
I noticed Goland by default seemed to have nicer syntax highlighting, so I started looking at
what I could do in VS Code to do this.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Before&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2022-12-27-til-how-to-add-custom-syntax-highlighting-rules-in-vs-code/images/before.png&#34;
        type=&#34;&#34;
        alt=&#34;Before&#34;
        
      /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;After&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2022-12-27-til-how-to-add-custom-syntax-highlighting-rules-in-vs-code/images/after.png&#34;
        type=&#34;&#34;
        alt=&#34;After&#34;
        
      /&gt;&lt;/p&gt;
&lt;p&gt;It turns out we can do this with our own custom rules using &lt;code&gt;editor.tokenColorCustomizations&lt;/code&gt;.
To do this go to our settings (&lt;code&gt;settings.json&lt;/code&gt;) and add something like:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How To Add Custom Syntax Highlighting Rules in VS Code</strong></p>
<p>The other day (a few months ago) I was comparing Goland and VS Code for Golang development.
I noticed Goland by default seemed to have nicer syntax highlighting, so I started looking at
what I could do in VS Code to do this.</p>
<blockquote>
<p>Before</p>
</blockquote>
<p><img
        loading="lazy"
        src="/posts/2022-12-27-til-how-to-add-custom-syntax-highlighting-rules-in-vs-code/images/before.png"
        type=""
        alt="Before"
        
      /></p>
<blockquote>
<p>After</p>
</blockquote>
<p><img
        loading="lazy"
        src="/posts/2022-12-27-til-how-to-add-custom-syntax-highlighting-rules-in-vs-code/images/after.png"
        type=""
        alt="After"
        
      /></p>
<p>It turns out we can do this with our own custom rules using <code>editor.tokenColorCustomizations</code>.
To do this go to our settings (<code>settings.json</code>) and add something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;editor.tokenColorCustomizations&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;[Dracula]&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;textMateRules&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nt">&#34;scope&#34;</span><span class="p">:</span> <span class="s2">&#34;source.go&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nt">&#34;settings&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;foreground&#34;</span><span class="p">:</span> <span class="s2">&#34;#6dd8ce&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Where:</p>
<ul>
<li><code>Dracula</code> is the colour scheme</li>
<li><code>scope</code> is the type of token we want to colour</li>
</ul>
<p>We can find out what we should set the scope as by going to the command palette and selecting <code>Developer: Inspect Editor Tokens and Scopes</code>.</p>
<p><img
        loading="lazy"
        src="/posts/2022-12-27-til-how-to-add-custom-syntax-highlighting-rules-in-vs-code/images/command.png"
        type=""
        alt="Command Palette"
        
      /></p>
<p>Then go and hover over the different parts of our code and it will tell you what type of token it is <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</p>
<p><img
        loading="lazy"
        src="/posts/2022-12-27-til-how-to-add-custom-syntax-highlighting-rules-in-vs-code/images/token.png"
        type=""
        alt="Token"
        
      /></p>
<p>That&rsquo;s it!</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://blog.4d.com/setting-up-code-syntax-highlighting-using-the-visual-studio-code-extension/">https://blog.4d.com/setting-up-code-syntax-highlighting-using-the-visual-studio-code-extension/</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Connect PocketBase and BackBlaze S3</title>
      <link>https://haseebmajid.dev/posts/2022-12-26-til-how-to-connect-pocketbase-and-backblaze-s3/</link>
      <pubDate>Mon, 26 Dec 2022 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2022-12-26-til-how-to-connect-pocketbase-and-backblaze-s3/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Connect PocketBase and BackBlaze S3&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;By default PocketBase uses the local file system to store uploaded files. If you have limited disk space, you could optionally connect to a S3 compatible storage. - PocketBase&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;why-backblaze-&#34;&gt;Why BackBlaze ?&lt;/h2&gt;
&lt;p&gt;Simple the prices are generally cheaper as compared with other S3 compatible storage platforms.
How can we set up BackBlaze as our S3 bucket to store uploaded files such as user avatars?&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Connect PocketBase and BackBlaze S3</strong></p>
<blockquote>
<p>By default PocketBase uses the local file system to store uploaded files. If you have limited disk space, you could optionally connect to a S3 compatible storage. - PocketBase</p>
</blockquote>
<h2 id="why-backblaze-">Why BackBlaze ?</h2>
<p>Simple the prices are generally cheaper as compared with other S3 compatible storage platforms.
How can we set up BackBlaze as our S3 bucket to store uploaded files such as user avatars?</p>
<h2 id="backblaze">BackBlaze</h2>
<p>Go to <code>BackBlaze</code></p>
<ul>
<li>Create an account</li>
<li>Create a new bucket
<ul>
<li>You should be able to make it public or private</li>
</ul>
</li>
</ul>
<p><img
        loading="lazy"
        src="/posts/2022-12-26-til-how-to-connect-pocketbase-and-backblaze-s3/images/bucket.png"
        type=""
        alt="Bucket"
        
      /></p>
<ul>
<li>Take note of the <code>endpoint</code> i.e. <code>s3.us-west-004.backblazeb2.com</code>.</li>
<li>Then go to <code>App Keys</code>
<ul>
<li>Create a new <code>Application key</code></li>
</ul>
</li>
</ul>
<p></p>
<ul>
<li>Take note of the <code>keyID</code> and <code>applicationKey</code></li>
</ul>
<h2 id="pocketbase">PocketBase</h2>
<p>Go to your PocketBase instance:</p>
<ul>
<li>Go to Settings</li>
<li>Go to File storage</li>
</ul>
<p>Add the following details</p>
<ul>
<li>In <code>ENDPOINT</code> add the endpoint above <code>https://s3.us-west-004.backblazeb2.com</code></li>
<li><code>BUCKET</code> is the name of the bucket we created before <code>ArticleTest</code></li>
<li><code>REGION</code> you can find in the URL <code>us-west-004</code></li>
<li><code>ACCESS KEY</code> is the <code>keyID</code> above</li>
<li><code>SECRET</code> is the <code>applicationKey</code> above</li>
</ul>
<p>Then click <code>Save changes</code> and you should see <code>S3 connected successfully</code>.</p>
<p>That&rsquo;s It!</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Use DinD, localhost &amp; Gitlab CI</title>
      <link>https://haseebmajid.dev/posts/2022-12-21-til-how-to-use-dind-localhost-gitlab-ci/</link>
      <pubDate>Wed, 21 Dec 2022 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2022-12-21-til-how-to-use-dind-localhost-gitlab-ci/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Use DinD, localhost &amp;amp; Gitlab CI&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In this post, I will go over how you can use docker-compose and Gitlab CI.
In this example, we will be running playwright tests directly on the Gitlab runner.
The tests will start a SvelteKit server also running on the Gitlab runner. The SvelteKit
server will connect to PocketBase (backend) running in docker-compose.&lt;/p&gt;
&lt;p&gt;So essentially we need a way for something running locally to connect to something running in
docker in Gitlab CI (on a runner). This is a pattern I am using in my new app &lt;a href=&#34;https://bookmarkey.app&#34;&gt;Bookmarkey&lt;/a&gt; &lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Use DinD, localhost &amp; Gitlab CI</strong></p>
<p>In this post, I will go over how you can use docker-compose and Gitlab CI.
In this example, we will be running playwright tests directly on the Gitlab runner.
The tests will start a SvelteKit server also running on the Gitlab runner. The SvelteKit
server will connect to PocketBase (backend) running in docker-compose.</p>
<p>So essentially we need a way for something running locally to connect to something running in
docker in Gitlab CI (on a runner). This is a pattern I am using in my new app <a href="https://bookmarkey.app">Bookmarkey</a> <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</p>
<p>Let&rsquo;s pretend we have a <code>docker-compose.yml</code> file which looks something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pocketbase</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">ghcr.io/muchobien/pocketbase:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s1">&#39;9090:8090&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./pb_data:/pb_data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/pb_public</span><span class="w">
</span></span></span></code></pre></div><p>and our <code>package.json</code> scripts section looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;scripts&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;test&#34;</span><span class="p">:</span> <span class="s2">&#34;playwright test&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Finally the most important file let&rsquo;s look at the gitlab ci file <code>.gitlab-ci.yml</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">tests:e2e</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_request</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">mcr.microsoft.com/playwright:v1.29.0-jammy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">docker:dind</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">DOCKER_DRIVER</span><span class="p">:</span><span class="w"> </span><span class="l">overlay2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">DOCKER_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">tcp://docker:2375</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">VITE_POCKET_BASE_URL</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;http://docker:9090&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="c"># ... Installing docker and docker compose</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">docker compose up -d</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">npm run test</span><span class="w">
</span></span></span></code></pre></div><p>Since all Gitlab CI jobs run in Docker. If we want to run docker-compose inside a job we need to use the dind service <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>.
We can then use docker normally i.e. using <code>docker compose</code> to start PocketBase. The most important line here is normally
to connect to PocketBase I would use <code>http://localhost:8090</code>. However, since we are using the <code>dind</code> service we need to use
<code>docker</code> instead of <code>localhost</code>. Hence this <code>VITE_POCKET_BASE_URL: 'http://docker:9090'</code>, passed to my SvelteKit app.</p>
<p>I spent about two days debugging this issue, even though I&rsquo;d solved this problem before 🤦‍♂️. So I decided to make a quick
post so hopefully you can avoid wasting your time.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://gitlab.com/banter-bus/bookmarkey/gui/-/blob/e575e5a97feb70227fd6aae366ce4fc9beacafe2/.gitlab-ci.yml">https://gitlab.com/banter-bus/bookmarkey/gui/-/blob/e575e5a97feb70227fd6aae366ce4fc9beacafe2/.gitlab-ci.yml</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Read more about <a href="/posts/20-05-01-how-to-use-dind-with-gitlab-ci/">dind here</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Setup OAuth2 with SvelteKit and Pocketbase</title>
      <link>https://haseebmajid.dev/posts/2022-12-20-how-to-setup-oauth2-with-sveltekit-and-pocketbase/</link>
      <pubDate>Tue, 20 Dec 2022 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2022-12-20-how-to-setup-oauth2-with-sveltekit-and-pocketbase/</guid>
      <description>&lt;details
  class=&#34;notice warning&#34;
  open=&#34;true&#34;
&gt;
    &lt;summary class=&#34;notice-title&#34;&gt;Out of date&lt;/summary&gt;
  
  This article maybe a bit out of date as PocketBase is adding new features and deprecating old functions. If you encounter
an issue with the setup &lt;a href=&#34;https://gitlab.com/hmajid2301/blog/-/issues/108&#34;&gt;see this issue by Dominick&lt;/a&gt;
&lt;/details&gt;

&lt;p&gt;Hi everyone, I&amp;rsquo;ve been building a new &lt;a href=&#34;https://gitlab.com/banter-bus/bookmarkey/gui&#34;&gt;bookmarking app&lt;/a&gt;, using SvelteKit and
PocketBase. &lt;a href=&#34;https://pocketbase.io/&#34;&gt;PocketBase&lt;/a&gt;, is an open-source backend, that we need to self-host &lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. It is written
in Golang, think of it similar to Firebase or Supabase.&lt;/p&gt;
&lt;p&gt;PocketBase will handle authentication for us, creating new users, and storing the password securely. You know things all apps
need, that we don&amp;rsquo;t want to spend a lot of times building ourselces. To learn more about authentication with SvelteKit I recommend checking out the web
there are some fantastic tutorials available &lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">Out of date</summary>
  
  This article maybe a bit out of date as PocketBase is adding new features and deprecating old functions. If you encounter
an issue with the setup <a href="https://gitlab.com/hmajid2301/blog/-/issues/108">see this issue by Dominick</a>
</details>

<p>Hi everyone, I&rsquo;ve been building a new <a href="https://gitlab.com/banter-bus/bookmarkey/gui">bookmarking app</a>, using SvelteKit and
PocketBase. <a href="https://pocketbase.io/">PocketBase</a>, is an open-source backend, that we need to self-host <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>. It is written
in Golang, think of it similar to Firebase or Supabase.</p>
<p>PocketBase will handle authentication for us, creating new users, and storing the password securely. You know things all apps
need, that we don&rsquo;t want to spend a lot of times building ourselces. To learn more about authentication with SvelteKit I recommend checking out the web
there are some fantastic tutorials available <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>.</p>
<p>In this post, we will look at how we can use OAuth providers such as Google, Github or Gitlab to authenticate with our app
and if needed to create a new account.</p>
<p>For an actual app using OAuth with SvelteKit <a href="https://gitlab.com/banter-bus/bookmarkey/gui/-/tree/22c3843ddb70d0002584efff0192140466d70283">click here</a>.</p>
<h2 id="pocketbase">PocketBase</h2>
<p>We can run pocketbase locally by using docker, create a <code>docker-compose.yml</code> which contains the following:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;3.7&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pocketbase</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">ghcr.io/muchobien/pocketbase:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s1">&#39;8090:8090&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/pb_data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/pb_public</span><span class="w">
</span></span></span></code></pre></div><h3 id="setup-auth-providers">Setup Auth Providers</h3>
<p>Then this will be available on <code>http://localhost:8090/_/</code> (after running <code>docker compose up</code>). Then set up our OAuth providers,
this process will vary from provider to provider. Let&rsquo;s setup up Gitlab in this post.</p>
<ul>
<li>Go to <a href="https://gitlab.com">https://gitlab.com</a></li>
<li>Go to <code>Preferences</code> (click on your avatar)</li>
<li>Then go to <code>Applications</code> (left bar)</li>
<li>Create a new application
<ul>
<li>Give it a name like <code>Bookmarkey Dev</code></li>
<li>Set the Redirect URI <code>http://localhost:5173/callback</code></li>
<li>Set <code>read_user</code> in scopes</li>
</ul>
</li>
</ul>
<p>Then set up the Auth provider on PocketBase.</p>
<ul>
<li>Go to <code>http://localhost:8090/_/</code></li>
<li>Go to <code>Settings &gt; Auth providers</code></li>
<li>Click on <code>GitLab</code> and check <code>Enable</code></li>
<li>Copy the <code>Application ID</code> (from Gitlab application) to the <code>CLIENT ID</code></li>
<li>Copy the <code>secret</code> to <code>CLIENT SECRET</code></li>
</ul>
<p>Now we have Gitlab as an OAuth provider enabled on PocketBase let&rsquo;s start coding on SvelteKit.</p>
<h2 id="sveltekit">SvelteKit</h2>
<p>I assume you already have a SvelteKit app if not you can follow the instructions here to create it <code>npm create svelte@latest example</code>.</p>
<p>Let&rsquo;s install PocketBase <code>npm install --save pocketbase</code>.</p>
<p>Then let&rsquo;s create a <code>src/hooks.server.ts</code>, which looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">import</span> <span class="kr">type</span> <span class="p">{</span> <span class="nx">Handle</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;@sveltejs/kit&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">PocketBase</span> <span class="kr">from</span> <span class="s1">&#39;pocketbase&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="nx">handle</span>: <span class="kt">Handle</span> <span class="o">=</span> <span class="kr">async</span> <span class="p">({</span> <span class="nx">event</span><span class="p">,</span> <span class="nx">resolve</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">event</span><span class="p">.</span><span class="nx">locals</span><span class="p">.</span><span class="nx">pb</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">PocketBase</span><span class="p">(</span><span class="s1">&#39;http://localhost:8090&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">event</span><span class="p">.</span><span class="nx">locals</span><span class="p">.</span><span class="nx">pb</span><span class="p">.</span><span class="nx">authStore</span><span class="p">.</span><span class="nx">loadFromCookie</span><span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">request</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="kr">get</span><span class="p">(</span><span class="s1">&#39;cookie&#39;</span><span class="p">)</span> <span class="o">||</span> <span class="s1">&#39;&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">locals</span><span class="p">.</span><span class="nx">pb</span><span class="p">.</span><span class="nx">authStore</span><span class="p">.</span><span class="nx">isValid</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">await</span> <span class="nx">event</span><span class="p">.</span><span class="nx">locals</span><span class="p">.</span><span class="nx">pb</span><span class="p">.</span><span class="nx">collection</span><span class="p">(</span><span class="s1">&#39;users&#39;</span><span class="p">).</span><span class="nx">authRefresh</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">event</span><span class="p">.</span><span class="nx">locals</span><span class="p">.</span><span class="nx">pb</span><span class="p">.</span><span class="nx">authStore</span><span class="p">.</span><span class="nx">clear</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">resolve</span><span class="p">(</span><span class="nx">event</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">isProd</span> <span class="o">=</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">NODE_ENV</span> <span class="o">===</span> <span class="s1">&#39;production&#39;</span> <span class="o">?</span> <span class="kc">true</span> <span class="o">:</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">response</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="kr">set</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s1">&#39;set-cookie&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">event</span><span class="p">.</span><span class="nx">locals</span><span class="p">.</span><span class="nx">pb</span><span class="p">.</span><span class="nx">authStore</span><span class="p">.</span><span class="nx">exportToCookie</span><span class="p">({</span> <span class="nx">secure</span>: <span class="kt">isProd</span><span class="p">,</span> <span class="nx">sameSite</span><span class="o">:</span> <span class="s1">&#39;Lax&#39;</span> <span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">response</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>This handle function acts as a bit of middleware between each of our requests. The <code>handle</code> function runs every time the
SvelteKit server receives a request <sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>. The function above we will be used to add the auth token to our request header.</p>
<p>Then if you are using Typescript go to your <code>app.d.ts</code> file and make it look like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="c1">// See https://kit.svelte.dev/docs/types#app
</span></span></span><span class="line"><span class="cl"><span class="c1">// for information about these interfaces
</span></span></span><span class="line"><span class="cl"><span class="c1">// and what to do when importing types
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">declare</span> <span class="kr">namespace</span> <span class="nx">App</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">type</span> <span class="nx">PocketBase</span> <span class="o">=</span> <span class="kr">import</span><span class="p">(</span><span class="s1">&#39;pocketbase&#39;</span><span class="p">).</span><span class="k">default</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kr">interface</span> <span class="nx">Locals</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">pb?</span>: <span class="kt">PocketBase</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// interface Error {}
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="c1">// interface Locals {}
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="c1">// interface PageData {}
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="c1">// interface Platform {}
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span></code></pre></div><h3 id="login">Login</h3>
<p>Next, let&rsquo;s go setup up the <code>login</code> page. Let&rsquo;s create a <code>src/routes/login/+page.server.ts</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-typescript" data-lang="typescript"><span class="line"><span class="cl"><span class="kr">import</span> <span class="kr">type</span> <span class="p">{</span> <span class="nx">PageServerLoad</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;./$types&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">type</span> <span class="nx">OutputType</span> <span class="o">=</span> <span class="p">{</span> <span class="nx">authProviderRedirect</span>: <span class="kt">string</span><span class="p">;</span> <span class="nx">authProviderState</span>: <span class="kt">string</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="nx">load</span>: <span class="kt">PageServerLoad</span><span class="p">&lt;</span><span class="nt">OutputType</span><span class="p">&gt;</span> <span class="o">=</span> <span class="kr">async</span> <span class="p">({</span> <span class="nx">locals</span><span class="p">,</span> <span class="nx">url</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">authMethods</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">locals</span><span class="p">.</span><span class="nx">pb</span><span class="o">?</span><span class="p">.</span><span class="nx">collection</span><span class="p">(</span><span class="s1">&#39;users&#39;</span><span class="p">).</span><span class="nx">listAuthMethods</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">authMethods</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">authProviderRedirect</span><span class="o">:</span> <span class="s1">&#39;&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nx">authProviderState</span><span class="o">:</span> <span class="s1">&#39;&#39;</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">redirectURL</span> <span class="o">=</span> <span class="sb">`</span><span class="si">${</span><span class="nx">url</span><span class="p">.</span><span class="nx">origin</span><span class="si">}</span><span class="sb">/account/callback`</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">gitlabAuthProvider</span> <span class="o">=</span> <span class="nx">authMethods</span><span class="p">.</span><span class="nx">authProviders</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">authProviderRedirect</span> <span class="o">=</span> <span class="sb">`</span><span class="si">${</span><span class="nx">gitlabAuthProvider</span><span class="p">.</span><span class="nx">authUrl</span><span class="si">}${</span><span class="nx">redirectURL</span><span class="si">}</span><span class="sb">`</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">state</span> <span class="o">=</span> <span class="nx">gitlabAuthProvider</span><span class="p">.</span><span class="nx">state</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">authProviderRedirect</span>: <span class="kt">authProviderRedirect</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">authProviderState</span>: <span class="kt">state</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>Here we get all the auth providers, in this case, we only want the information from the first one.
As we only have one provider, we can get the first one (Gitlab). Then we return the <code>state</code> and the
redirect URL to the OAuth provider.</p>
<p>What is returned from this <code>PageServerLoad</code> can be accessed by the Svelte page. Let&rsquo;s see how
we can do this, create an <code>src/routes/login/+page.svelte</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-svelte" data-lang="svelte"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">lang</span><span class="o">=</span><span class="s">&#34;ts&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="kr">import</span> <span class="p">{</span> <span class="nx">browser</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;$app/environment&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kr">import</span> <span class="kr">type</span> <span class="p">{</span> <span class="nx">PageData</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;./$types&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kr">export</span> <span class="kd">let</span> <span class="nx">data</span>: <span class="kt">PageData</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">function</span> <span class="nx">gotoAuthProvider() {</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nx">browser</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nb">document</span><span class="p">.</span><span class="nx">cookie</span> <span class="o">=</span> <span class="sb">`state=</span><span class="si">${</span><span class="nx">data</span><span class="o">?</span><span class="p">.</span><span class="nx">authProviderState</span><span class="si">}</span><span class="sb">`</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">href</span> <span class="o">=</span> <span class="nx">data</span><span class="p">.</span><span class="nx">authProviderRedirect</span> <span class="o">||</span> <span class="s1">&#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">button</span> <span class="na">on:click</span><span class="o">=</span><span class="p">{</span><span class="nx">gotoAuthProvider</span><span class="p">}</span><span class="err">&gt;</span><span class="s">Login</span> <span class="na">with</span> <span class="na">GitLab</span><span class="err">&lt;/</span><span class="na">button</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>We can access what is returned in the <code>.server.ts</code> file by using the <code>data</code> variable.
Here when the user clicks the button to go login with Gitlab. We save the state
in the cookie, which we will compare later on. Then we redirect them to the OAuth login
for Gitlab. After they have authenticated with GitLab, they will be redirected back to our
site but redirected to the <code>/callback</code> route. As we configured above.</p>
<details
  class="notice tip"
  open="true"
>
    <summary class="notice-title">Types</summary>
  
  Types are auto-generated when we run <code>svelte-kit sync</code> or <code>vite dev</code>.
These types automatically work out what the <code>PageServerLoad</code> function returns.
When we type our <code>data</code> as <code>PageData</code>.
</details>

<h2 id="callback">Callback</h2>
<p>Let&rsquo;s create a new page called <code>src/routes/callback/+server.ts</code>, this page will have the
logic to authenticate (and create an account if needed).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">redirect</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;@sveltejs/kit&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="kr">type</span> <span class="p">{</span> <span class="nx">RequestEvent</span><span class="p">,</span> <span class="nx">RequestHandler</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;./$types&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="nx">GET</span>: <span class="kt">RequestHandler</span> <span class="o">=</span> <span class="kr">async</span> <span class="p">({</span> <span class="nx">locals</span><span class="p">,</span> <span class="nx">url</span><span class="p">,</span> <span class="nx">cookies</span> <span class="p">}</span><span class="o">:</span> <span class="nx">RequestEvent</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">redirectURL</span> <span class="o">=</span> <span class="sb">`</span><span class="si">${</span><span class="nx">url</span><span class="p">.</span><span class="nx">origin</span><span class="si">}</span><span class="sb">/callback`</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">expectedState</span> <span class="o">=</span> <span class="nx">cookies</span><span class="p">.</span><span class="kr">get</span><span class="p">(</span><span class="s1">&#39;state&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">query</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">URLSearchParams</span><span class="p">(</span><span class="nx">url</span><span class="p">.</span><span class="nx">search</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">state</span> <span class="o">=</span> <span class="nx">query</span><span class="p">.</span><span class="kr">get</span><span class="p">(</span><span class="s1">&#39;state&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">code</span> <span class="o">=</span> <span class="nx">query</span><span class="p">.</span><span class="kr">get</span><span class="p">(</span><span class="s1">&#39;code&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">authMethods</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">locals</span><span class="p">.</span><span class="nx">pb</span><span class="o">?</span><span class="p">.</span><span class="nx">collection</span><span class="p">(</span><span class="s1">&#39;users&#39;</span><span class="p">).</span><span class="nx">listAuthMethods</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">authMethods</span><span class="o">?</span><span class="p">.</span><span class="nx">authProviders</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;authy providers&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">throw</span> <span class="nx">redirect</span><span class="p">(</span><span class="mi">303</span><span class="p">,</span> <span class="s1">&#39;/login&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">provider</span> <span class="o">=</span> <span class="nx">authMethods</span><span class="p">.</span><span class="nx">authProviders</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">provider</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;Provider not found&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">throw</span> <span class="nx">redirect</span><span class="p">(</span><span class="mi">303</span><span class="p">,</span> <span class="s1">&#39;/login&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">expectedState</span> <span class="o">!==</span> <span class="nx">state</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;state does not match expected&#39;</span><span class="p">,</span> <span class="nx">expectedState</span><span class="p">,</span> <span class="nx">state</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">throw</span> <span class="nx">redirect</span><span class="p">(</span><span class="mi">303</span><span class="p">,</span> <span class="s1">&#39;/login&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">await</span> <span class="nx">locals</span><span class="p">.</span><span class="nx">pb</span>
</span></span><span class="line"><span class="cl">            <span class="o">?</span><span class="p">.</span><span class="nx">collection</span><span class="p">(</span><span class="s1">&#39;users&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">.</span><span class="nx">authWithOAuth2</span><span class="p">(</span><span class="nx">provider</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span> <span class="nx">code</span> <span class="o">||</span> <span class="s1">&#39;&#39;</span><span class="p">,</span> <span class="nx">provider</span><span class="p">.</span><span class="nx">codeVerifier</span><span class="p">,</span> <span class="nx">redirectURL</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;Error logging in with 0Auth user&#39;</span><span class="p">,</span> <span class="nx">err</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">throw</span> <span class="nx">redirect</span><span class="p">(</span><span class="mi">303</span><span class="p">,</span> <span class="s1">&#39;/&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>Here we are grabbing our auth provider, comparing the state is what we expect in cookie. Then using pocketbase
to check the query parameters sent to the redirect URI are all valid, as someone could of course spoof this.
Try to authenticate as someone else.</p>
<p>The main auth logic happens here:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="k">await</span> <span class="nx">locals</span><span class="p">.</span><span class="nx">pb</span>
</span></span><span class="line"><span class="cl">    <span class="o">?</span><span class="p">.</span><span class="nx">collection</span><span class="p">(</span><span class="s1">&#39;users&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nx">authWithOAuth2</span><span class="p">(</span><span class="nx">provider</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span> <span class="nx">code</span> <span class="o">||</span> <span class="s1">&#39;&#39;</span><span class="p">,</span> <span class="nx">provider</span><span class="p">.</span><span class="nx">codeVerifier</span><span class="p">,</span> <span class="nx">redirectURL</span><span class="p">);</span>
</span></span></code></pre></div><p>Using Pocketbase means we don&rsquo;t have to write any of the logic ourselves in a backend service.
It&rsquo;ll handle this interaction, it is even smart enough to create a new user if none is associated
with the email the user authenticates.</p>
<p>Finally, if everything worked we redirect them to the home page <code>throw redirect(303, '/');</code>.
We can then check if the user is logged in using <code>locals.pb.authStore.isValid</code>.
Again we would use the pattern we saw above with <code>+page.server.ts</code> and <code>+page.svelte</code>.
To pass it in as data, to the Svelte components/page.</p>
<h2 id="layout">Layout</h2>
<p>If we want to check someone if logged we could do something like this create a <code>src/routes/+layout.server.ts</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">import</span> <span class="kr">type</span> <span class="p">{</span> <span class="nx">LayoutServerLoad</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;./$types&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">type</span> <span class="nx">OutputType</span> <span class="o">=</span> <span class="p">{</span> <span class="nx">isLoggedIn</span>: <span class="kt">boolean</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="nx">load</span>: <span class="kt">LayoutServerLoad</span><span class="p">&lt;</span><span class="nt">OutputType</span><span class="p">&gt;</span> <span class="o">=</span> <span class="kr">async</span> <span class="p">({</span> <span class="nx">locals</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">isLoggedIn</span>: <span class="kt">locals.pb?.authStore.isValid</span> <span class="o">?</span> <span class="kc">true</span> <span class="o">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>Then we can access this in <code>src/routes/foo/+page.svelte</code> as:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-svelte" data-lang="svelte"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">lang</span><span class="o">=</span><span class="s">&#34;ts&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="kr">import</span> <span class="kr">type</span> <span class="p">{</span> <span class="nx">PageData</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;./$types&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kr">export</span> <span class="kd">let</span> <span class="nx">data</span>: <span class="kt">PageData</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">isLoggedIn</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>That&rsquo;s it! We can now authenticate users using OAuth and Pocketbase. I recommend <a href="https://www.youtube.com/watch?v=UbhhJWV3bmI">this video</a>
for some caveats when try to project routes.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/banter-bus/bookmarkey/gui/-/tree/537c7c71e5529c3c1351d98a8a632d9244b16e41/src">Bookmarkey App using this pattern</a></li>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2022-12-20-how-to-setup-oauth2-with-sveltekit-and-pocketbase/source_code/example">Example source code</a></li>
</ul>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://github.com/pocketbase/pocketbase/discussions/537">https://github.com/pocketbase/pocketbase/discussions/537</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://www.youtube.com/watch?v=vKqWED-aPMg">https://www.youtube.com/watch?v=vKqWED-aPMg</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p><a href="https://kit.svelte.dev/docs/hooks#server-hooks-handle">https://kit.svelte.dev/docs/hooks#server-hooks-handle</a>&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to use DotBot to personalise your VSCode Devcontainers</title>
      <link>https://haseebmajid.dev/posts/2022-12-15-how-to-use-dotbot-to-personalise-your-vscode-devcontainers/</link>
      <pubDate>Thu, 15 Dec 2022 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2022-12-15-how-to-use-dotbot-to-personalise-your-vscode-devcontainers/</guid>
      <description>&lt;details
  class=&#34;notice warning&#34;
  open=&#34;true&#34;
&gt;
    &lt;summary class=&#34;notice-title&#34;&gt;Devcontainers&lt;/summary&gt;
  
  This article assumes you are already familiar with dev containers.
You can read more about &lt;a href=&#34;https://code.visualstudio.com/docs/devcontainers/containers&#34;&gt;devcontainers here&lt;/a&gt;.
&lt;/details&gt;

&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2022-12-15-how-to-use-dotbot-to-personalise-your-vscode-devcontainers/images/say-docker.jpeg&#34;
        type=&#34;&#34;
        alt=&#34;Docker Meme&#34;
        
      /&gt;&lt;/p&gt;
&lt;p&gt;In this article, we will go over how you can personalise your dev containers. Devcontainers allow us to create consistent development environments. One of the main advantages of dev containers is we can provide a &amp;ldquo;one button&amp;rdquo; setup for new developers.
We do this by using a container (Docker), and we end up developing inside a container. Much like if we used &lt;code&gt;docker exec -it ubuntu /bin/bash&lt;/code&gt;.
Except it provides a few nice conveniences such as copying (into the container) over the project files and our ssh keys.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">Devcontainers</summary>
  
  This article assumes you are already familiar with dev containers.
You can read more about <a href="https://code.visualstudio.com/docs/devcontainers/containers">devcontainers here</a>.
</details>

<p><img
        loading="lazy"
        src="/posts/2022-12-15-how-to-use-dotbot-to-personalise-your-vscode-devcontainers/images/say-docker.jpeg"
        type=""
        alt="Docker Meme"
        
      /></p>
<p>In this article, we will go over how you can personalise your dev containers. Devcontainers allow us to create consistent development environments. One of the main advantages of dev containers is we can provide a &ldquo;one button&rdquo; setup for new developers.
We do this by using a container (Docker), and we end up developing inside a container. Much like if we used <code>docker exec -it ubuntu /bin/bash</code>.
Except it provides a few nice conveniences such as copying (into the container) over the project files and our ssh keys.</p>
<p>However one of the issues that can arise from this is how you get your dev tools/programs in the dev container.
For example, I use fish shell but lots of Docker containers default to using bash. I also don&rsquo;t want to pollute the Docker file
with a bunch of my specific dev tools. If every developer does that you could end up with a very large Docker file.
This will also mean it takes longer for the dev container to build.</p>
<p>One way we can do this is by using DotBot and a dotfiles repo. I will assume you are familiar with everything we&rsquo;ve covered up to this point.
You have a dotfiles repo which uses DotBot, has profiles and has plugins installed. In this example, we will be using the <a href="https://github.com/bryant1410/dotbot-apt"><code>dotbot-apt</code> plugin</a>.</p>
<h2 id="dotfiles">Dotfiles</h2>
<p>Let&rsquo;s go to our dotfiles repo which we will assume looks like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">├── ....
</span></span><span class="line"><span class="cl">├── bashrc
</span></span><span class="line"><span class="cl">├── fish
</span></span><span class="line"><span class="cl">│   └── fish.config
</span></span><span class="line"><span class="cl">├── .gitconfig
</span></span><span class="line"><span class="cl">├── install-profile
</span></span><span class="line"><span class="cl">├── install-standalone
</span></span><span class="line"><span class="cl">├── meta
</span></span><span class="line"><span class="cl">│   ├── configs
</span></span><span class="line"><span class="cl">│   │   └── git.yaml
</span></span><span class="line"><span class="cl">│   ├── dotbot
</span></span><span class="line"><span class="cl">│   ├── dotbot-apt
</span></span><span class="line"><span class="cl">│   ├── base.yaml
</span></span><span class="line"><span class="cl">│   └── profiles
</span></span><span class="line"><span class="cl">│       └── linux
</span></span><span class="line"><span class="cl">└── vscode
</span></span></code></pre></div><h3 id="configs">configs</h3>
<p>It may look something like the above. Let&rsquo;s create some new configs specific to our dev container. In this case, we will assume all the
dev containers we will use will be Debian based. So let&rsquo;s create a file <code>meta/configs/packages.debian.yaml</code> which may look like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">apt</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">jq</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">fzf</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">vim</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">make</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">zoxide</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">exa</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">fish</span><span class="w">
</span></span></span></code></pre></div><p>This will be used to install the specific dev tools I need such as <code>jq</code> and <code>fzf</code>. Next, I want to make sure my fish shell config also gets set up
correctly so we will create another file called <code>meta/configs/shell.yaml</code> which looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">link</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">~/.config/fish</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">fish/**</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">glob</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">create</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span></code></pre></div><h3 id="profiles">profiles</h3>
<p>This will copy (symlink) all of my fish config files to <code>~/.config/fish/</code> directory in the dev container from the dotfiles repo.
Next, let us create a new profile <code>meta/profiles/devcontainer</code> which will look like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">packages.debian-sudo
</span></span><span class="line"><span class="cl">shell
</span></span></code></pre></div><p>Remember by appending <code>-sudo</code> to <code>packages.debian</code> we will run those directives as root i.e. <code>apt</code>.</p>
<h3 id="install-script">Install Script</h3>
<p>So what we have done is create a new profile which will install some of the dev tools we need and copy over our fish config.
Now we have to do one final thing create a new file at the root called <code>install.devcontainer.sh</code> (you can call this whatever
you want, just remember the name). This file looks like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env bash
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl">./install-profile devcontainer
</span></span></code></pre></div><p>The reason we need this we need to provide an executable file in our VS Code config. We couldn&rsquo;t just run specify this
<code>./install-profile devcontainer</code>. We will see this a bit later.</p>
<h3 id="structure">Structure</h3>
<p>Our repo structure now looks like</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">├── ....
</span></span><span class="line"><span class="cl">├── bashrc
</span></span><span class="line"><span class="cl">├── fish
</span></span><span class="line"><span class="cl">│   └── fish.config
</span></span><span class="line"><span class="cl">├── .gitconfig
</span></span><span class="line"><span class="cl">├── install-profile
</span></span><span class="line"><span class="cl">├── install-standalone
</span></span><span class="line"><span class="cl">├── meta
</span></span><span class="line"><span class="cl">│   ├── configs
</span></span><span class="line"><span class="cl">│   │   ├── shell.yaml
</span></span><span class="line"><span class="cl">│   │   ├── packages.debian.yaml
</span></span><span class="line"><span class="cl">│   │   └── git.yaml
</span></span><span class="line"><span class="cl">│   ├── dotbot
</span></span><span class="line"><span class="cl">│   ├── dotbot-apt
</span></span><span class="line"><span class="cl">│   ├── base.yaml
</span></span><span class="line"><span class="cl">│   └── profiles
</span></span><span class="line"><span class="cl">│       └── linux
</span></span><span class="line"><span class="cl">└── vscode
</span></span></code></pre></div><p>Now let&rsquo;s move on to the repository that is using dev containers. We are going to use a super simple example,
just to demonstrate. Let&rsquo;s create a new file <code>.devcontainer/devcontainer.json</code> which looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;Go&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;image&#34;</span><span class="p">:</span> <span class="s2">&#34;mcr.microsoft.com/devcontainers/go:0-1.18&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;features&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;ghcr.io/devcontainers/features/node:1&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;version&#34;</span><span class="p">:</span> <span class="s2">&#34;lts&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Configure tool-specific properties.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="nt">&#34;customizations&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Configure properties specific to VS Code.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nt">&#34;vscode&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="c1">// Set *default* container specific settings.json values on container create.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>      <span class="nt">&#34;settings&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;go.toolsManagement.checkForUpdates&#34;</span><span class="p">:</span> <span class="s2">&#34;local&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;go.useLanguageServer&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;go.gopath&#34;</span><span class="p">:</span> <span class="s2">&#34;/go&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Use &#39;forwardPorts&#39; to make a list of ports inside the container available locally.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="c1">// &#34;forwardPorts&#34;: [],
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl">  <span class="c1">// Use &#39;postCreateCommand&#39; to run commands after the container is created.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="c1">// &#34;postCreateCommand&#34;: &#34;go version&#34;,
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl">  <span class="c1">// Set `remoteUser` to `root` to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="nt">&#34;remoteUser&#34;</span><span class="p">:</span> <span class="s2">&#34;vscode&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This is the default one generated by VS Code for Golang projects (when using the command palette). Normally we would have a custom Docker image
we are using. Perhaps in a future post, I will go over how to use dev containers with an existing custom Docker image. But for this example,
we will just use the Microsoft provided golang image <code>mcr.microsoft.com/devcontainers/go:0-1.18</code>.</p>
<details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">Extension</summary>
  
  You need to have the <a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers">devcontainer extension</a> installed in VS Code.
</details>

<p>This is enough to create a dev container, we can open the command palette on VS Code and run <code>rebuild and reopen in container</code>. However
this is one final thing we need to do.</p>
<p>Open your <code>settings.json</code> file and add something like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="nt">&#34;dotfiles.repository&#34;</span><span class="p">:</span> <span class="s2">&#34;hmajid2301/dotfiles&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;dotfiles.targetPath&#34;</span><span class="p">:</span> <span class="s2">&#34;~/dotfiles&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;dotfiles.installCommand&#34;</span><span class="p">:</span> <span class="s2">&#34;~/dotfiles/install.devcontainer.sh&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span></code></pre></div><ul>
<li><code>dotfiles.repository</code>: You will need to update the repo <code>hmajid2301/dotfiles</code> to point to your dotfiles repo and it must be accessible on github.</li>
<li><code>dotfiles.targetPath</code>: The <code>targetPath</code> is where in the devcontainer we will git clone our dotfiles repo.</li>
<li><code>dotfiles.installCommand</code>: The executable it will run after the devcontainer is set up. If you called it something else you will need to update that here as well.</li>
</ul>
<p>That&rsquo;s it, now we can have a common dev container set up and personalise with our dotfiles and specific dev tools we want using DotBot.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/dotfiles/-/tree/6b83e990861654506e8ecc756af75cf431438a4a">My Dotfiles</a></li>
<li><a href="https://gitlab.com/hmajid2301/dotfiles/-/blob/77ee6056ae1a1b4ad066348e2b6a3dd6109a409a/meta/profiles/devcontainer">My devcontainer DotBot profile</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Use Prettier with Hugo/Golang Templates</title>
      <link>https://haseebmajid.dev/posts/2022-12-12-til-how-to-use-prettier-with-hugo-golang-templates/</link>
      <pubDate>Mon, 12 Dec 2022 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2022-12-12-til-how-to-use-prettier-with-hugo-golang-templates/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Use Prettier with Hugo/Golang Templates&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you try to use prettier on a (Hugo) Golang HTML template you may get something that looks like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go-html-template&#34; data-lang=&#34;go-html-template&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;{{-&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;or&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;na&#34;&gt;.Params.author&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;site&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;.Params.author&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;cp&#34;&gt;}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;cp&#34;&gt;{{-&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;$author&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;.Params.author&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;default&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;site&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;.Params.author&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;cp&#34;&gt;}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;cp&#34;&gt;{{-&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;$author_type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;printf&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;%T&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;$author&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;cp&#34;&gt;}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;cp&#34;&gt;{{-&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;or&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;eq&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;$author_type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;[]string&amp;#34;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;eq&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;$author_type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;[]interface {}&amp;#34;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;))&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;cp&#34;&gt;}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;cp&#34;&gt;{{-&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;delimit&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;$author&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;, &amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;cp&#34;&gt;}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;cp&#34;&gt;{{-&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;else&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;cp&#34;&gt;}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;cp&#34;&gt;{{-&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;$author&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;cp&#34;&gt;}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;cp&#34;&gt;{{-&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;cp&#34;&gt;}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;{{-&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;cp&#34;&gt;-}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;into this&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Use Prettier with Hugo/Golang Templates</strong></p>
<p>If you try to use prettier on a (Hugo) Golang HTML template you may get something that looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go-html-template" data-lang="go-html-template"><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="k">or</span><span class="w"> </span><span class="na">.Params.author</span><span class="w"> </span><span class="nx">site</span><span class="na">.Params.author</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl">  <span class="cp">{{-</span><span class="w"> </span><span class="nx">$author</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">(</span><span class="na">.Params.author</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">default</span><span class="w"> </span><span class="nx">site</span><span class="na">.Params.author</span><span class="o">)</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl">  <span class="cp">{{-</span><span class="w"> </span><span class="nx">$author_type</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">(</span><span class="k">printf</span><span class="w"> </span><span class="s">&#34;%T&#34;</span><span class="w"> </span><span class="nx">$author</span><span class="o">)</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl">  <span class="cp">{{-</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="o">(</span><span class="k">or</span><span class="w"> </span><span class="o">(</span><span class="k">eq</span><span class="w"> </span><span class="nx">$author_type</span><span class="w"> </span><span class="s">&#34;[]string&#34;</span><span class="o">)</span><span class="w"> </span><span class="o">(</span><span class="k">eq</span><span class="w"> </span><span class="nx">$author_type</span><span class="w"> </span><span class="s">&#34;[]interface {}&#34;</span><span class="o">))</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl">    <span class="cp">{{-</span><span class="w"> </span><span class="o">(</span><span class="nx">delimit</span><span class="w"> </span><span class="nx">$author</span><span class="w"> </span><span class="s">&#34;, &#34;</span><span class="w"> </span><span class="o">)</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl">  <span class="cp">{{-</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl">    <span class="cp">{{-</span><span class="w"> </span><span class="nx">$author</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl">  <span class="cp">{{-</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="cp">-}}</span>
</span></span></code></pre></div><p>into this</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go-html-template" data-lang="go-html-template"><span class="line"><span class="cl"><span class="cp">{{-</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="k">or</span><span class="w"> </span><span class="na">.Params.author</span><span class="w"> </span><span class="nx">site</span><span class="na">.Params.author</span><span class="w"> </span><span class="cp">}}</span> <span class="cp">{{-</span><span class="w"> </span><span class="nx">$author</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">(</span><span class="na">.Params.author</span><span class="w"> </span><span class="o">|</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nx">default</span><span class="w"> </span><span class="nx">site</span><span class="na">.Params.author</span><span class="o">)</span><span class="w"> </span><span class="cp">}}</span> <span class="cp">{{-</span><span class="w"> </span><span class="nx">$author_type</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">(</span><span class="k">printf</span><span class="w"> </span><span class="s">&#34;%T&#34;</span><span class="w"> </span><span class="nx">$author</span><span class="o">)</span><span class="w"> </span><span class="cp">}}</span> <span class="cp">{{-</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">if</span><span class="w"> </span><span class="o">(</span><span class="k">or</span><span class="w"> </span><span class="o">(</span><span class="k">eq</span><span class="w"> </span><span class="nx">$author_type</span><span class="w"> </span><span class="s">&#34;[]string&#34;</span><span class="o">)</span><span class="w"> </span><span class="o">(</span><span class="k">eq</span><span class="w"> </span><span class="nx">$author_type</span><span class="w"> </span><span class="s">&#34;[]interface {}&#34;</span><span class="o">))</span><span class="w"> </span><span class="cp">}}</span> <span class="cp">{{-</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="o">(</span><span class="nx">delimit</span><span class="w"> </span><span class="nx">$author</span><span class="w"> </span><span class="s">&#34;, &#34;</span><span class="w"> </span><span class="o">)</span><span class="w"> </span><span class="cp">}}</span> <span class="cp">{{-</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="cp">}}</span> <span class="cp">{{-</span><span class="w"> </span><span class="nx">$author</span><span class="w"> </span><span class="cp">}}</span> <span class="cp">{{-</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="cp">}}</span> <span class="cp">{{-</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="cp">-}}</span>
</span></span></code></pre></div><p>This of course something we don&rsquo;t want. So let&rsquo;s use a prettier plugin that can solve this problem for us.
Let us install <code>npm install --save-dev prettier-plugin-go-template</code>.</p>
<p>Create a <code>.prettierrc</code> file with the following contents:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;overrides&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;files&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;*.html&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;options&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;parser&#34;</span><span class="p">:</span> <span class="s2">&#34;go-template&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;goTemplateBracketSpacing&#34;</span><span class="p">:</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Prettier will now format our HTML files correctly, in our Hugo project.</p>
<h2 id="optional-settings">Optional Settings</h2>
<p>By default the format of our files like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">a</span>
</span></span><span class="line"><span class="cl">  <span class="na">href</span><span class="o">=</span><span class="s">&#34;https://github.com/reorx/hugo-PaperModX/&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="na">rel</span><span class="o">=</span><span class="s">&#34;noopener&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="na">target</span><span class="o">=</span><span class="s">&#34;_blank&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">&gt;&lt;/</span><span class="nt">a</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>If we add the following option to the <code>.prettierrc</code>, <code>&quot;bracketSameLine&quot;: true</code> then our file will look like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">a</span>
</span></span><span class="line"><span class="cl">  <span class="na">href</span><span class="o">=</span><span class="s">&#34;https://github.com/reorx/hugo-PaperModX/&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="na">rel</span><span class="o">=</span><span class="s">&#34;noopener&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="na">target</span><span class="o">=</span><span class="s">&#34;_blank&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">&gt;&lt;/</span><span class="nt">a</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>Sometimes our file may look something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">span</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  Analytics by
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;https://{{ .Site.Params.goatcounter }}.goatcounter.com&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&gt;</span>Goatcounter.<span class="p">&lt;/</span><span class="nt">a</span>
</span></span><span class="line"><span class="cl">  <span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>To fix the dangling <code>&gt;</code> we can add the following option to the <code>.prettierrc</code> file ,  <code>&quot;htmlWhitespaceSensitivity&quot;: &quot;ignore&quot;</code> then our
file will look like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">span</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  Analytics by
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;https://{{ .Site.Params.goatcounter }}.goatcounter.com&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    Goatcounter.
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span>
</span></span></code></pre></div>]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Deploy a SvelteKit site on Netlify</title>
      <link>https://haseebmajid.dev/posts/2022-12-10-til-how-to-deploy-a-sveltekit-site-on-netlify/</link>
      <pubDate>Sat, 10 Dec 2022 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2022-12-10-til-how-to-deploy-a-sveltekit-site-on-netlify/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Deploy a SvelteKit site on Netlify&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;So you have a SvelteKit site you want to deploy to Netlify how can we do that? First, go about setting up a site like normal!
In my case, I am going to import an existing site from Gitlab.&lt;/p&gt;
&lt;h2 id=&#34;netlify&#34;&gt;Netlify&lt;/h2&gt;
&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2022-12-10-til-how-to-deploy-a-sveltekit-site-on-netlify/images/new_netlify_site.png&#34;
        type=&#34;&#34;
        alt=&#34;New Netlify Site&#34;
        
      /&gt;&lt;/p&gt;
&lt;p&gt;In the build settings we want the following settings:&lt;/p&gt;
&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2022-12-10-til-how-to-deploy-a-sveltekit-site-on-netlify/images/import_site.png&#34;
        type=&#34;&#34;
        alt=&#34;Import from git&#34;
        
      /&gt;&lt;/p&gt;
&lt;details
  class=&#34;notice tip&#34;
  open=&#34;true&#34;
&gt;
    &lt;summary class=&#34;notice-title&#34;&gt;Branch&lt;/summary&gt;
  
  Adjust the branch to whatever your main branch is i.e. the branch you want to deploy from.
&lt;/details&gt;

&lt;p&gt;That&amp;rsquo;s it now our site should start deploying, and it should also set up deploy previews for MRs automatically
&lt;a href=&#34;https://haseebmajid.dev/posts/2022-12-03-my-workflow-to-create-a-new-post-using-hugo-netlifycms-netlify-and-gitlab-together/#netlify-preview&#34;&gt;More about that here&lt;/a&gt;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Deploy a SvelteKit site on Netlify</strong></p>
<p>So you have a SvelteKit site you want to deploy to Netlify how can we do that? First, go about setting up a site like normal!
In my case, I am going to import an existing site from Gitlab.</p>
<h2 id="netlify">Netlify</h2>
<p><img
        loading="lazy"
        src="/posts/2022-12-10-til-how-to-deploy-a-sveltekit-site-on-netlify/images/new_netlify_site.png"
        type=""
        alt="New Netlify Site"
        
      /></p>
<p>In the build settings we want the following settings:</p>
<p><img
        loading="lazy"
        src="/posts/2022-12-10-til-how-to-deploy-a-sveltekit-site-on-netlify/images/import_site.png"
        type=""
        alt="Import from git"
        
      /></p>
<details
  class="notice tip"
  open="true"
>
    <summary class="notice-title">Branch</summary>
  
  Adjust the branch to whatever your main branch is i.e. the branch you want to deploy from.
</details>

<p>That&rsquo;s it now our site should start deploying, and it should also set up deploy previews for MRs automatically
<a href="/posts/2022-12-03-my-workflow-to-create-a-new-post-using-hugo-netlifycms-netlify-and-gitlab-together/#netlify-preview">More about that here</a>.</p>
<h2 id="our-project">Our Project</h2>
<p>Within our SvelteKit project if you look at <code>svelte.config.js</code> it should look something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">adapter</span> <span class="nx">from</span> <span class="s1">&#39;@sveltejs/adapter-auto&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">preprocess</span> <span class="nx">from</span> <span class="s1">&#39;svelte-preprocess&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/** @type {import(&#39;@sveltejs/kit&#39;).Config} */</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">config</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// Consult https://github.com/sveltejs/svelte-preprocess
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="c1">// for more information about preprocessors
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="nx">kit</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">adapter</span><span class="o">:</span> <span class="nx">adapter</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nx">preprocess</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="nx">preprocess</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">      <span class="nx">postcss</span><span class="o">:</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl">  <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">config</span><span class="p">;</span>
</span></span></code></pre></div><p>You do not need to use the Netlify adapter here unless we need to specify specific options such as edge functions etc. However to let SvelteKit know we are using the Netlify adapter we need to pass an environment variable <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</p>
<p>We can also create a <code>netlify.toml</code> file repository to keep the settings locally rather than just on Netlify. The advantage of this is keeping
our settings in code (and version control).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">build</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">publish</span> <span class="p">=</span> <span class="s2">&#34;build&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">command</span> <span class="p">=</span> <span class="s2">&#34;npm run build&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">context</span><span class="p">.</span><span class="nx">production</span><span class="p">.</span><span class="nx">environment</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">NETLIFY</span> <span class="p">=</span> <span class="s2">&#34;true&#34;</span>
</span></span></code></pre></div><div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://github.com/sveltejs/kit/blob/master/packages/adapter-auto/adapters.js">https://github.com/sveltejs/kit/blob/master/packages/adapter-auto/adapters.js</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Update a Project using a Copier Template</title>
      <link>https://haseebmajid.dev/posts/2022-12-09-how-to-update-a-project-using-a-copier-template/</link>
      <pubDate>Thu, 08 Dec 2022 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2022-12-09-how-to-update-a-project-using-a-copier-template/</guid>
      <description>&lt;p&gt;In this article, I will show you how you can update a project, that was created from a copier template.
In my &lt;a href=&#34;https://haseebmajid.dev/posts/2022-12-01-how-to-use-copier-to-create-project-templates/&#34;&gt;previous article&lt;/a&gt;, we learnt how we can create
a project template. I listed the only reason I choose to use copier over say cookiecutter was that it provided an
&amp;ldquo;easy&amp;rdquo; way to update downstream projects.&lt;/p&gt;
&lt;details
  class=&#34;notice tip&#34;
  open=&#34;true&#34;
&gt;
    &lt;summary class=&#34;notice-title&#34;&gt;Existing Repository&lt;/summary&gt;
  
  From here on out we will assume you have a template repository that uses &lt;code&gt;copier&lt;/code&gt;.
Here is an &lt;a href=&#34;https://gitlab.com/banter-bus/fastapi-template&#34;&gt;example repository&lt;/a&gt;.
&lt;/details&gt;

&lt;h2 id=&#34;update&#34;&gt;Update&lt;/h2&gt;
&lt;details
  class=&#34;notice tip&#34;
  open=&#34;true&#34;
&gt;
    &lt;summary class=&#34;notice-title&#34;&gt;Terminology&lt;/summary&gt;
  
  &lt;p&gt;Some terminology:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this article, I will show you how you can update a project, that was created from a copier template.
In my <a href="/posts/2022-12-01-how-to-use-copier-to-create-project-templates/">previous article</a>, we learnt how we can create
a project template. I listed the only reason I choose to use copier over say cookiecutter was that it provided an
&ldquo;easy&rdquo; way to update downstream projects.</p>
<details
  class="notice tip"
  open="true"
>
    <summary class="notice-title">Existing Repository</summary>
  
  From here on out we will assume you have a template repository that uses <code>copier</code>.
Here is an <a href="https://gitlab.com/banter-bus/fastapi-template">example repository</a>.
</details>

<h2 id="update">Update</h2>
<details
  class="notice tip"
  open="true"
>
    <summary class="notice-title">Terminology</summary>
  
  <p>Some terminology:</p>
<ul>
<li>Template Repository: The repository is built using <code>copier</code>. This is the repository which we use to create projects.</li>
<li>Downstream Project: A project created using the template repository. This is the project we will deploy into production.</li>
</ul>
</details>

<p>To be able to use <code>copier update</code> to update a downstream project we need to meet the following conditions:</p>
<ul>
<li>The template includes a valid .copier-answers.yml file.</li>
<li>The template is versioned with Git (with tags).</li>
<li>The downstream project is versioned with Git.</li>
</ul>
<p>In this example we will assume these URLs:</p>
<ul>
<li>Template Repository: <a href="https://gitlab.com/banter-bus/fastapi-template/">https://gitlab.com/banter-bus/fastapi-template/</a></li>
<li>Downstream Project: <a href="https://gitlab.com/banter-bus/banter-bus-management-api">https://gitlab.com/banter-bus/banter-bus-management-api</a></li>
</ul>
<h3 id="change-a-file">Change a file</h3>
<p>So let&rsquo;s pretend we have a template repository, and we make an update to it. Say we add a new make target like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-makefile" data-lang="makefile"><span class="line"><span class="cl"><span class="nv">.DEFAULT_GOAL</span> <span class="o">:=</span> <span class="nb">help</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">.PHONY</span><span class="o">:</span> <span class="n">help</span>
</span></span><span class="line"><span class="cl"><span class="nf">help</span><span class="o">:</span> <span class="c">## Generates a help README
</span></span></span><span class="line"><span class="cl"><span class="c"></span>	@grep -E <span class="s1">&#39;^[a-zA-Z_-]+:.*?## .*$$&#39;</span> <span class="k">$(</span>MAKEFILE_LIST<span class="k">)</span> <span class="p">|</span> sort <span class="p">|</span> awk <span class="s1">&#39;BEGIN {FS = &#34;:.*?## &#34;}; {printf &#34;\033[36m%-30s\033[0m %s\n&#34;, $$1, $$2}&#39;</span>
</span></span></code></pre></div><p>Now rather than copy this manually to each of our downstream projects/ We can automate this process a bit.
So first things first, we need to commit this change on the template repository and then create a new git tag.
This is the recommended way to version the templates repository. i.e. <code>git tag 0.3.1</code> and then you can do <code>git push --tags</code>.
To publish the tags on your remote repository.</p>
<p>As a slight aside I also keep track of my changes in a <code>CHANGELOG.md</code> using <a href="https://keepachangelog.com/en/1.0.0/">keepachangelog</a> format.
Which directly relates to git tags.</p>
<h3 id="updating-downstream-project">Updating downstream project</h3>
<p>Once we have published and tagged the change say with <code>0.3.1</code>. The change is now ready to be pulled in from our downstream projects.
Now let&rsquo;s go to our downstream project. Assume the <code>.copier-answers.yml</code> looks something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># Changes here will be overwritten by Copier</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">_commit</span><span class="p">:</span><span class="w"> </span><span class="m">0.3.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">_src_path</span><span class="p">:</span><span class="w"> </span><span class="l">gl:banter-bus/fastapi-template</span><span class="w">
</span></span></span></code></pre></div><p>We can run <code>copier update</code> and it should pull in the latest changes to the makefile in our project. It will also update the <code>_commit: 0.3.1</code>.
This is how we keep track of what version of the template this project is currently using. You shouldn&rsquo;t update this file.</p>
<blockquote>
<p>If you need to install <code>copier</code> you can read how to do it <a href="https://copier.readthedocs.io/en/stable/#installation">here</a>.</p>
</blockquote>
<details
  class="notice tip"
  open="true"
>
    <summary class="notice-title">More Details</summary>
  
  If you want more specifics about how copier does its update you can <a href="https://copier.readthedocs.io/en/stable/updating/#never-change-the-answers-file-manually">read about it here</a>.
</details>

<h2 id="thats-it">That&rsquo;s It!</h2>
<p>So that&rsquo;s it! We looked at how we can update our downstream projects when we use <code>copier</code> to template our repository.
One other thing we could look at doing is trying to automate this further, perhaps by automatically creating PR/MRs, to all
downstream projects when we update thte template. We would run <code>copier update</code> and create a new branch, that a human could then
review. A bit like dependabot for dependencies in GitHub.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://copier.readthedocs.io/en/stable/updating/">Updating a project</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Separate our Golang Tests</title>
      <link>https://haseebmajid.dev/posts/2022-12-04-til-how-to-separate-our-golang-tests/</link>
      <pubDate>Sun, 04 Dec 2022 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2022-12-04-til-how-to-separate-our-golang-tests/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Separate our Golang Tests&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Sometimes we want to be able to run our unit tests and integration tests separately.
In Golang we can do this using build tags, build tags are used to tell the compiler
important information when we run &lt;code&gt;go build&lt;/code&gt; &lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Let say we have a file called &lt;code&gt;package_test.go&lt;/code&gt;. By adding &lt;code&gt;// +build integration&lt;/code&gt; to the top of the file
without any whitespace. This test file will only be run when we specify the tags in our
test command &lt;code&gt;go test --tags=integration&lt;/code&gt;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Separate our Golang Tests</strong></p>
<p>Sometimes we want to be able to run our unit tests and integration tests separately.
In Golang we can do this using build tags, build tags are used to tell the compiler
important information when we run <code>go build</code> <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</p>
<p>Let say we have a file called <code>package_test.go</code>. By adding <code>// +build integration</code> to the top of the file
without any whitespace. This test file will only be run when we specify the tags in our
test command <code>go test --tags=integration</code>.</p>
<p>Our integration test file would look something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-golang" data-lang="golang"><span class="line"><span class="cl"><span class="c1">// +build integration
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="kn">package</span> <span class="nx">mypackage_test</span>
</span></span></code></pre></div><p>Our unit tests can be left without any build tags and can be run with <code>go test</code> like normal.</p>
<p>To get this to compile correctly with VS Code we need to add the following to <code>settings.json</code> file.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;go.buildFlags&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;-tags=integration&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;go.testTags&#34;</span><span class="p">:</span> <span class="s2">&#34;integration&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>More about Golang build tags, <a href="https://mickey.dev/posts/go-build-tags-testing/">https://mickey.dev/posts/go-build-tags-testing/</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>My Workflow To Create a New Post Using Hugo, NetlifyCMS, Netlify and Gitlab Together</title>
      <link>https://haseebmajid.dev/posts/2022-12-03-my-workflow-to-create-a-new-post-using-hugo-netlifycms-netlify-and-gitlab-together/</link>
      <pubDate>Sat, 03 Dec 2022 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2022-12-03-my-workflow-to-create-a-new-post-using-hugo-netlifycms-netlify-and-gitlab-together/</guid>
      <description>&lt;details
  class=&#34;notice warning&#34;
  open=&#34;true&#34;
&gt;
    &lt;summary class=&#34;notice-title&#34;&gt;Netlify CMS Deprecated&lt;/summary&gt;
  
  &lt;p&gt;Since I have written this post Netlify CMS has been deprecated in favour of decap-cms.
Thanks to @roneo for raising this &lt;a href=&#34;https://gitlab.com/hmajid2301/blog/-/issues/101&#34;&gt;issue&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So the Netlify CMS part of this article is no longer relevant. I also no longer use this
workflow, I instead just create articles locally using a script now!!!&lt;/p&gt;

&lt;/details&gt;

&lt;p&gt;In this post, I will go over my new workflow for creating articles/posts that I now use with
my new (Hugo) blog. Though I&amp;rsquo;m sure much of this will apply to other JAM stack sites as well.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">Netlify CMS Deprecated</summary>
  
  <p>Since I have written this post Netlify CMS has been deprecated in favour of decap-cms.
Thanks to @roneo for raising this <a href="https://gitlab.com/hmajid2301/blog/-/issues/101">issue</a>.</p>
<p>So the Netlify CMS part of this article is no longer relevant. I also no longer use this
workflow, I instead just create articles locally using a script now!!!</p>

</details>

<p>In this post, I will go over my new workflow for creating articles/posts that I now use with
my new (Hugo) blog. Though I&rsquo;m sure much of this will apply to other JAM stack sites as well.</p>
<details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">Hugo Site</summary>
  
  <p>Before carrying on, this post assumes you have an existing Hugo blog.</p>
<p>My Hugo blog setup looked something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">├── archetypes
</span></span><span class="line"><span class="cl">├── config.yml
</span></span><span class="line"><span class="cl">├── content
</span></span><span class="line"><span class="cl">├── docker-compose.yml
</span></span><span class="line"><span class="cl">├── Dockerfile
</span></span><span class="line"><span class="cl">├── go.mod
</span></span><span class="line"><span class="cl">├── README.md
</span></span><span class="line"><span class="cl">├── resources
</span></span><span class="line"><span class="cl">├── static
</span></span><span class="line"><span class="cl">├── Taskfile.yml
</span></span><span class="line"><span class="cl">└── themes
</span></span></code></pre></div><p>We also assume you are using <a href="https://gohugo.io/content-management/page-bundles/">page bundles</a>.</p>

</details>

<h2 id="netlify">Netlify</h2>
<p>First, let&rsquo;s use Netlify to deploy our website. Netlify is a great website that allows
us to deploy our Hugo blog for free. It has a very generous free tier. At the time of writing
100 GB bandwidth per month and 300 build minutes. To be honest my blog is more
than enough and if we ever need more at that point I can probably afford to pay for the
premium tiers.</p>
<p>Netlify makes it dead simple to deploy our Hugo blog all we need to do is link our Git repo,
which in our case is on <a href="https://gitlab.com/hmajid2301/blog">Gitlab</a>.</p>
<p>First, create a Netlify account if you need to, and then it will ask you to create a new team.
A project will belong to a team, you can add other users to the team. Allowing them to
manage the site on Netlify as well. In my case, I am the only one in my team as it is for my
blog.</p>
<p><img
        loading="lazy"
        src="/posts/2022-12-03-my-workflow-to-create-a-new-post-using-hugo-netlifycms-netlify-and-gitlab-together/images/netlify_team.png"
        type=""
        alt="Netlify Sites"
        
      /></p>
<p>You can add a new site; link it to your Gitlab repository. It will be set up to automatically
deploy all changes from the main/master branch. So anytime you make a commit to your main branch.
Netlify will redeploy your website. Which is exactly what we want. Especially since Hugo
websites tend to build quite fast, we are unlikely to use the full 300 build minutes in Netlify.</p>
<p>I used to use Gitlab to build and deploy my site but now Gitlab has restricted the build minutes
per month by a lot. So I let Netlify deploy for us. My deploy settings look like this:</p>
<p><img
        loading="lazy"
        src="/posts/2022-12-03-my-workflow-to-create-a-new-post-using-hugo-netlifycms-netlify-and-gitlab-together/images/netlify_deploy_settings.png"
        type=""
        alt="Netlify Deploy Settings"
        
      /></p>
<p>One plugin I&rsquo;d recommend which I think speeds up builds on Netlify is <code>Hugo cache resources</code>.</p>
<p><img
        loading="lazy"
        src="/posts/2022-12-03-my-workflow-to-create-a-new-post-using-hugo-netlifycms-netlify-and-gitlab-together/images/netlify_plugins.png"
        type=""
        alt="Netlify Plugins"
        
      /></p>
<h3 id="identity">Identity</h3>
<p>We will need to enable identity to restrict access to the CMS. To turn on identity do the following:</p>
<ul>
<li>Go to Settings &gt; Identity, and select Enable Identity service.</li>
<li>Once enabled, select Settings and usage, and scroll down to Registration preferences. You can set this to either Open or Invite only, but usually Invite only is your best bet for a personal site.</li>
<li>If you don&rsquo;t want to create an account, or would like to use an external provider such as GitHub or Google, you can enable those services under External providers.</li>
<li>Scroll down to Services and click Enable Git Gateway.</li>
</ul>
<p>We will need to use this later after we&rsquo;ve set up NetlifyCMS.</p>
<p><details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">Gitlab Gateway Issues</summary>
  
  <p>With changes to Gitlab 15 and expiring OAuth tokens, <a href="https://docs.netlify.com/visitor-access/git-gateway/#troubleshoot-git-gateway-connection-issues-with-gitlab">see more here</a>.</p>
<p>You need to use a personal access token to authenticate instead of your normal password.
Copy the token and keep it somewhere safe i.e. your password manager.</p>
<p>Then go to your Git Gateway settings in <code>Site Settings &gt; Identity</code> (on Netlify).
Then edit and repalce the access token with the one you created above.</p>
<p>This should avoid the issue where you cannot access <code>/admin</code> after 2 hours.
Then need to disable and re-enable git gateway (getting a new OAuth JWT).
Which was quite annoying todo.</p>

</details>

<img
        loading="lazy"
        src="/posts/2022-12-03-my-workflow-to-create-a-new-post-using-hugo-netlifycms-netlify-and-gitlab-together/images/gitlab_access_token.png"
        type=""
        alt="PAT"
        
      /></p>
<p><img
        loading="lazy"
        src="/posts/2022-12-03-my-workflow-to-create-a-new-post-using-hugo-netlifycms-netlify-and-gitlab-together/images/netlify_git_gateway.png"
        type=""
        alt="Netlify Gateway"
        
      /></p>
<h3 id="netlify-preview">Netlify Preview</h3>
<p>One setting you may have noticed that we turned on is using deploy previews. Which allows us to</p>
<blockquote>
<p>Deploy Previews allow you and your team to experience changes to any part of your site without having to publish them to production. - Netlify</p>
</blockquote>
<p>This great feature will provide us with a unique deploy link in each of our merge requests
we create on Gitlab. So we can preview the website without needing to commit to the main
branch. Which has helped me catch a bunch of issues with my site.</p>
<p>Make sure to have this notification set up (also in the <code>Build &amp; Deploy</code> settings in the screenshots above).</p>
<p><img
        loading="lazy"
        src="/posts/2022-12-03-my-workflow-to-create-a-new-post-using-hugo-netlifycms-netlify-and-gitlab-together/images/netlify_deploy_notifications.png"
        type=""
        alt="Deploy Notifications"
        
      /></p>
<p>We will see in a bit more detail how this is used later on.</p>
<h2 id="netlify-cms">Netlify CMS</h2>
<p>Now that we have our blog deploying using Netlify. Let&rsquo;s look at setting up Netlify CMS.
Netlify CMS is an &ldquo;Open source content management for your Git workflow&rdquo;. It super easy to
setup and will integrate very nicely with our site.</p>
<h3 id="page-bundles">Page Bundles</h3>
<p>Before we look at how we can add Netlify CMS to our Hugo blog. First, let&rsquo;s quickly look at
(leaf) page bundles. I like using page bundles because it allows me to keep related to a post. For example the project structure
would look like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">content/posts/2022-08-08-running-gitlab-ci-jobs-in-docker-using-docker-compose/
</span></span><span class="line"><span class="cl">├── images
</span></span><span class="line"><span class="cl">│   └── cover.png
</span></span><span class="line"><span class="cl">└── index.md
</span></span></code></pre></div><p>You can see we have the image associated with this page as a subdirectory and the content of the post is in the <code>index.md</code> file.
For every post, all the associated files/content are within their own folder and it is nice and contained.</p>
<details
  class="notice tip"
  open="true"
>
    <summary class="notice-title">URL</summary>
  
  The folder path here <code>content/posts/2022-08-08-running-gitlab-ci-jobs-in-docker-using-docker-compose/</code>. This page would
then be accessed at
<a href="https://haseebmajid.dev/posts/2022-08-08-running-gitlab-ci-jobs-in-docker-using-docker-compose/">https://haseebmajid.dev/posts/2022-08-08-running-gitlab-ci-jobs-in-docker-using-docker-compose/</a>.
</details>

<p><a href="https://gitlab.com/hmajid2301/blog/-/tree/51e1231b2d0f94e6d8a0158dd3c34a8ffe84a896/content/posts/2022-11-15-til-you-can-hash-none-in-python-what">Example page bundle here</a></p>
<p>The only reason I mention page bundles is that to use Hugo page bundles with Netlify CMS we will have to adjust the Netlify CMS configuration a bit.</p>
<h3 id="setup-cms">Setup CMS</h3>
<p>Now let&rsquo;s add <a href="https://www.netlifycms.org/docs/hugo/">Netlify CMS</a> to our blog.</p>
<p>In your <code>static</code> folder create a new folder called <code>admin</code> i.e. <code>static/admin/</code>.</p>
<h4 id="configyml">config.yml</h4>
<p>Create a new file called <code>static/admin/config.yml</code>, which looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">backend</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">git-gateway</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">branch</span><span class="p">:</span><span class="w"> </span><span class="l">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">use_large_media_transforms_in_media_library</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">media_folder</span><span class="p">:</span><span class="w"> </span><span class="l">static/images</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">public_folder</span><span class="p">:</span><span class="w"> </span><span class="l">/images</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w"></span><span class="nt">publish_mode</span><span class="p">:</span><span class="w"> </span><span class="l">editorial_workflow</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">collections</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;blog&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">label</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Blog&#34;</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">    </span><span class="nt">folder</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;content/posts&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># Support Hugo page bundles that puts index.md and images in folders named by slug</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">    </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;{{slug}}/index&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">media_folder</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;images&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">public_folder</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;images&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">create</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">    </span><span class="nt">slug</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;{{fields.date}}-{{slug}}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">editor</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">preview</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">fields</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- {<span class="w"> </span><span class="nt">label</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Title&#34;</span><span class="nt">, name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;title&#34;</span><span class="nt">, widget</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;string&#34;</span><span class="w"> </span>}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- {<span class="w"> </span><span class="nt">label</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Canonical URL&#34;</span><span class="nt">, name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;canonicalURL&#34;</span><span class="nt">, widget</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;hidden&#34;</span><span class="w"> </span>}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- {<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">label</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Publish Date&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;date&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">widget</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;date&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">format</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;YYYY-MM-DD&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- {<span class="w"> </span><span class="nt">label</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Tags&#34;</span><span class="nt">, name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;tags&#34;</span><span class="nt">, widget</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;list&#34;</span><span class="nt">, allow_add</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span>}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- {<span class="w"> </span><span class="nt">label</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Series&#34;</span><span class="nt">, name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;series&#34;</span><span class="nt">, widget</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;list&#34;</span><span class="nt">, allow_add</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span>}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">label</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Cover&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;cover&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">widget</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;object&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">collapsed</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">fields</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span>- {<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">label</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Image&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;image&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">default</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;images/cover.png&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">              </span><span class="nt">widget</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;string&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span>}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- {<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">label</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Images&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;thumbnail&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">widget</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;image&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">choose_url</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">required</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">media_library</span><span class="p">:</span><span class="w"> </span>{<span class="w"> </span><span class="nt">config</span><span class="p">:</span><span class="w"> </span>{<span class="w"> </span><span class="nt">multiple</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span>}<span class="w"> </span>}<span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>}<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- {<span class="w"> </span><span class="nt">label</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Body&#34;</span><span class="nt">, name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;body&#34;</span><span class="nt">, widget</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;markdown&#34;</span><span class="w"> </span>}<span class="w">
</span></span></span></code></pre></div><p>I have highlighted some lines of interest in that file.</p>
<ul>
<li><code>backend.branch</code>: Change this to the name of the main branch of your git repository on Gitlab i.e. <code>master</code>.</li>
<li><code>publish_mode: editorial_workflow</code>: This setting means when we make changes on the Netlify CMS i.e. create a new post it will create a new MR, this helps to keep the main branch git history nicer. It also allows you to review the post before it goes out. You can see this in the photos below.</li>
<li><code>folder: &quot;content/posts&quot;</code>: Here we specify where to create new posts in.</li>
<li><code>path: &quot;{{slug}}/index&quot;</code>:  Here is where we specify the page bundle part it will create the markdown file using the slug i.e. title and date and create an index.md file. For example, if our post title was <code>My Workflow To Create a New Post Using Hugo, Netlifycms, Netlify and Gitlab Together</code> we would create a file at <code>content/posts/2022-12-03-my-workflow-to-create-a-new-post-using-hugo,-netlifycms,-netlify-and-gitlab-together/index.md</code>.</li>
<li><code>slug: &quot;{{fields.date}}-{{slug}}&quot;</code>: Then we specify the slug which essentially is the name of the file created. Here we specify to use the date combined with the slug. For example, like this <code>2022-12-03-my-workflow-to-create-a-new-post-using-hugo,-netlifycms,-netlify-and-gitlab-together</code>.</li>
</ul>
<p><img
        loading="lazy"
        src="/posts/2022-12-03-my-workflow-to-create-a-new-post-using-hugo-netlifycms-netlify-and-gitlab-together/images/netlify_cms_editorial.png"
        type=""
        alt="Netlify CMS Editorial"
        
      /></p>
<h5 id="fields">fields</h5>
<p>Finally, the fields section is what will be used to fill the frontmatter of our post I want it to be something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl">---
</span></span><span class="line"><span class="cl">title: &#34;TIL: How you can add goatcounter to your Hugo blog&#34;
</span></span><span class="line"><span class="cl">canonicalURL: https://haseebmajid.dev/posts/2022-11-20-til-how-you-can-add-goatcounter-to-your-hugo-blog/
</span></span><span class="line"><span class="cl">date: 2022-11-20
</span></span><span class="line"><span class="cl">tags:
</span></span><span class="line"><span class="cl">  <span class="k">-</span> hugo
</span></span><span class="line"><span class="cl">  <span class="k">-</span> blog
</span></span><span class="line"><span class="cl">  <span class="k">-</span> goatcounter
</span></span><span class="line"><span class="cl">series:
</span></span><span class="line"><span class="cl">  <span class="k">-</span> TIL
</span></span><span class="line"><span class="cl">  <span class="k">-</span> Goatcounter with Hugo
</span></span><span class="line"><span class="cl">cover:
</span></span><span class="line"><span class="cl">  image: images/cover.png
</span></span><span class="line"><span class="cl">---
</span></span></code></pre></div><p>Note <code>canonicalURL</code> is dynamically created and we will see how this is created, hence it is a <code>hidden</code> field which we cannot edit within NetlifyCMS.
The fields look like this:</p>
<p><img
        loading="lazy"
        src="/posts/2022-12-03-my-workflow-to-create-a-new-post-using-hugo-netlifycms-netlify-and-gitlab-together/images/netlify_cms_blog.png"
        type=""
        alt="Netlify CMS Blog Post"
        
      /></p>
<p>Note to have nested fields in <code>frontmatter</code> we can do something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl">- <span class="nt">label</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Cover&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;cover&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">widget</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;object&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">collapsed</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">fields</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- {<span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">label</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Image&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;image&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">default</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;images/cover.png&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">widget</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;string&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>}<span class="w">
</span></span></span></code></pre></div><p>To get <code>cover.images: images/cover.png</code>. You will need to set the fields as you need them to be for your front matter.</p>
<h4 id="indexhtml">index.html</h4>
<p>Create a new file called <code>static/admin/index.html</code>, which looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="cp">&lt;!DOCTYPE html&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">html</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">head</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">meta</span> <span class="na">charset</span><span class="o">=</span><span class="s">&#34;utf-8&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">meta</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;viewport&#34;</span> <span class="na">content</span><span class="o">=</span><span class="s">&#34;width=device-width, initial-scale=1.0&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">title</span><span class="p">&gt;</span>Content Manager<span class="p">&lt;/</span><span class="nt">title</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="c">&lt;!-- Include the script that enables Netlify Identity on this page. --&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;https://identity.netlify.com/v1/netlify-identity-widget.js&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">head</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">body</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="c">&lt;!-- Include the script that builds the page and powers Netlify CMS --&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;https://unpkg.com/netlify-cms@^2.0.0/dist/netlify-cms.js&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">body</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">CMS</span><span class="p">.</span><span class="nx">registerEventListener</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">      <span class="nx">name</span><span class="o">:</span> <span class="s2">&#34;preSave&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">handler</span><span class="o">:</span> <span class="p">({</span> <span class="nx">entry</span> <span class="p">})</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kr">const</span> <span class="nx">slug</span> <span class="o">=</span> <span class="nx">entry</span>
</span></span><span class="line"><span class="cl">          <span class="p">.</span><span class="nx">getIn</span><span class="p">([</span><span class="s2">&#34;data&#34;</span><span class="p">,</span> <span class="s2">&#34;title&#34;</span><span class="p">],</span> <span class="s2">&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">          <span class="p">.</span><span class="nx">toLowerCase</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">          <span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/[&#39;]/g</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">          <span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/[.]/g</span><span class="p">,</span> <span class="s2">&#34;-&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">          <span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/[\s]/g</span><span class="p">,</span> <span class="s2">&#34;-&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">          <span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="s2">&#34;:&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="kr">const</span> <span class="nx">date</span> <span class="o">=</span> <span class="nx">entry</span><span class="p">.</span><span class="nx">getIn</span><span class="p">([</span><span class="s2">&#34;data&#34;</span><span class="p">,</span> <span class="s2">&#34;date&#34;</span><span class="p">],</span> <span class="s2">&#34;&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nx">entry</span>
</span></span><span class="line"><span class="cl">          <span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s2">&#34;data&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">          <span class="p">.</span><span class="nx">set</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;canonicalURL&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="sb">`https://haseebmajid.dev/posts/</span><span class="si">${</span><span class="nx">date</span><span class="si">}</span><span class="sb">-</span><span class="si">${</span><span class="nx">slug</span><span class="si">}</span><span class="sb">/`</span>
</span></span><span class="line"><span class="cl">          <span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">html</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>Now as stated above I wanted to dynamically set the <code>canonicalURL</code> when the file was created we do this using this in the script section.
We use the <code>preSave</code> hook provided to us by the NetlifyCMS library. Where we get the slug before the file is saved and concat it with the current
date. This is not ideal however as now if we change how the file name is generated above in the <code>config.yaml</code> (<code>slug: &quot;{{fields.date}}-{{slug}}&quot;</code>),
we will have to adjust our logic here as well. I will see if I can come up with a better way to do this.</p>
<h2 id="gitlab">Gitlab</h2>
<p>We don&rsquo;t have to do anything in Gitlab, what will happen when we create a new blog post is that a new MR will be created.
It will start with the <code>draft</code> tag and change as we move the posts into the different sections in our workflow (see the image above),
<code>Drafts</code>, <code>In Review</code> and <code>Ready</code>. When we publish the post it will auto-merge the MR in Gitlab.</p>
<p><img
        loading="lazy"
        src="/posts/2022-12-03-my-workflow-to-create-a-new-post-using-hugo-netlifycms-netlify-and-gitlab-together/images/gitlab_mr.png"
        type=""
        alt="Netlify CMS Gitlab MR"
        
      /></p>
<p>Since we turned on deploy previews on MRs we will also get to preview the website, this allows us to preview the website before merging to the main branch.</p>
<p><img
        loading="lazy"
        src="/posts/2022-12-03-my-workflow-to-create-a-new-post-using-hugo-netlifycms-netlify-and-gitlab-together/images/gitlab_mr_preview.png"
        type=""
        alt="Gitlab MR Preview"
        
      /></p>
<p>I have also editted my <code>netlify.toml</code> file with the following command <code>--buildFuture</code>, so I can also preview posts that will be published
in the future.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">context</span><span class="p">.</span><span class="nx">deploy-preview</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">command</span> <span class="p">=</span> <span class="s2">&#34;hugo --gc --minify --buildFuture -b $DEPLOY_PRIME_URL&#34;</span>
</span></span></code></pre></div><h2 id="putting-it-all-together">Putting it all together</h2>
<p>Now let&rsquo;s put this all together to access Netlify CMS go to your website <code>/admin</code> (after you&rsquo;ve deployed your changes).
So in my case, it would be <code>https://haseebmajid.dev/admin</code>, log in with your relevant account i.e. Gitlab or Github, depending on what
was enabled in your identity settings.</p>
<p>Then create a new post, after you have clicked save it will get moved to draft and create an MR on GitLab. After a few minutes, you
should see a preview environment as a comment from Netlify to preview your site. After you publish the post or merge the MR. The
post your site will start to deploy via Netlify.</p>
<div style="position:relative;padding-bottom:calc(100% / 1.78)">
  <iframe
    src="https://gfycat.com/ifr/flamboyantillustriousgiantschnauzer"
    frameborder="0"
    scrolling="no"
    width="100%"
    height="100%"
    style="position:absolute;top:0;left:0;"
    allowfullscreen
  ></iframe>
</div>

<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/4b19bc2347bad46215f6b32518c19052338a2c78">My site using this workflow</a></li>
<li><a href="https://www.netlifycms.org/docs/hugo/">Official Netlify CMS Hugo Integration Docs</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>My Useful Vim Commands (with VS Code)</title>
      <link>https://haseebmajid.dev/posts/2022-12-02-my-useful-vim-commands-with-vs-code/</link>
      <pubDate>Fri, 02 Dec 2022 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2022-12-02-my-useful-vim-commands-with-vs-code/</guid>
      <description>&lt;p&gt;In this post, I will show you some useful and perhaps not as well-known Vim commands I use with VS Code.
As per &lt;a href=&#34;https://haseebmajid.dev/2022-10-16-my-current-vscode-setup-extensions-and-settings/&#34;&gt;this previous post&lt;/a&gt;, I use
&lt;a href=&#34;https://marketplace.visualstudio.com/items?itemName=vscodevim.vim&#34;&gt;this extension&lt;/a&gt; for Vim emulation in VS Code.&lt;/p&gt;
&lt;p&gt;My &lt;a href=&#34;https://gitlab.com/hmajid2301/dotfiles/-/blob/6b83e990861654506e8ecc756af75cf431438a4a/vscode/linux/keybindings.json&#34;&gt;keybindings.json&lt;/a&gt; file.&lt;/p&gt;
&lt;p&gt;I will repeat some of the settings I showed there for posterity.&lt;/p&gt;
&lt;h2 id=&#34;useful-commands&#34;&gt;Useful Commands&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;xp&lt;/code&gt;: To swap two characters around  i.e. &lt;code&gt;ab&lt;/code&gt; -&amp;gt; &lt;code&gt;ba&lt;/code&gt; (cursor on &lt;code&gt;a&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctrl + enter&lt;/code&gt;: Creates a new line below and stays in &lt;code&gt;NORMAL&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;o&lt;/code&gt;: Will do the same but put you in insert mode&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ctrl + shift + enter&lt;/code&gt;: Creates a new line above and stays in &lt;code&gt;NORMAL&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;O&lt;/code&gt;: Will do the same but put you in insert mode&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dtx&lt;/code&gt;: Deletes everything up to &lt;code&gt;x&lt;/code&gt; character&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dfx&lt;/code&gt;: Delete everything including &lt;code&gt;x&lt;/code&gt; character&lt;/li&gt;
&lt;li&gt;&lt;code&gt;yi&amp;quot;&lt;/code&gt;: Yank everything between quotes &lt;code&gt;&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vi&amp;quot;&amp;quot;+p&lt;/code&gt;: Replace everything between quotes with what is yanked&lt;/li&gt;
&lt;li&gt;&lt;code&gt;va{Vy&lt;/code&gt; &lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;: Yank the entire function assuming the language uses braces like &lt;code&gt;golang&lt;/code&gt; or &lt;code&gt;C&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;_dd&lt;/code&gt; &lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;: Delete without adding to register&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;aY&lt;/code&gt; &lt;sup id=&#34;fnref1:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;: Yank into named register &lt;code&gt;a&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;ap&lt;/code&gt; &lt;sup id=&#34;fnref2:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;: Paste from named register &lt;code&gt;a&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;%x``df(&lt;/code&gt; &lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;: Deletes surrounding function i.e. &lt;code&gt;func(arg)&lt;/code&gt; -&amp;gt; &lt;code&gt;arg&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;commands-as-settings&#34;&gt;Commands as settings&lt;/h3&gt;
&lt;p&gt;To make some of these commands easier to use we can do something like:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this post, I will show you some useful and perhaps not as well-known Vim commands I use with VS Code.
As per <a href="/2022-10-16-my-current-vscode-setup-extensions-and-settings/">this previous post</a>, I use
<a href="https://marketplace.visualstudio.com/items?itemName=vscodevim.vim">this extension</a> for Vim emulation in VS Code.</p>
<p>My <a href="https://gitlab.com/hmajid2301/dotfiles/-/blob/6b83e990861654506e8ecc756af75cf431438a4a/vscode/linux/keybindings.json">keybindings.json</a> file.</p>
<p>I will repeat some of the settings I showed there for posterity.</p>
<h2 id="useful-commands">Useful Commands</h2>
<ul>
<li><code>xp</code>: To swap two characters around  i.e. <code>ab</code> -&gt; <code>ba</code> (cursor on <code>a</code>)</li>
<li><code>ctrl + enter</code>: Creates a new line below and stays in <code>NORMAL</code>
<ul>
<li><code>o</code>: Will do the same but put you in insert mode</li>
</ul>
</li>
<li><code>ctrl + shift + enter</code>: Creates a new line above and stays in <code>NORMAL</code>
<ul>
<li><code>O</code>: Will do the same but put you in insert mode</li>
</ul>
</li>
<li><code>dtx</code>: Deletes everything up to <code>x</code> character</li>
<li><code>dfx</code>: Delete everything including <code>x</code> character</li>
<li><code>yi&quot;</code>: Yank everything between quotes <code>&quot;</code></li>
<li><code>vi&quot;&quot;+p</code>: Replace everything between quotes with what is yanked</li>
<li><code>va{Vy</code> <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>: Yank the entire function assuming the language uses braces like <code>golang</code> or <code>C</code></li>
<li><code>&quot;_dd</code> <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>: Delete without adding to register</li>
<li><code>&quot;aY</code> <sup id="fnref1:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>: Yank into named register <code>a</code></li>
<li><code>&quot;ap</code> <sup id="fnref2:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>: Paste from named register <code>a</code></li>
<li><code>%x``df(</code> <sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>: Deletes surrounding function i.e. <code>func(arg)</code> -&gt; <code>arg</code></li>
</ul>
<h3 id="commands-as-settings">Commands as settings</h3>
<p>To make some of these commands easier to use we can do something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;vim.normalModeKeyBindingsNonRecursive&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;before&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;y&#34;</span><span class="p">,</span> <span class="s2">&#34;a&#34;</span><span class="p">,</span> <span class="s2">&#34;f&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;after&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;v&#34;</span><span class="p">,</span> <span class="s2">&#34;a&#34;</span><span class="p">,</span> <span class="s2">&#34;{&#34;</span><span class="p">,</span> <span class="s2">&#34;V&#34;</span><span class="p">,</span> <span class="s2">&#34;y&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;before&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;d&#34;</span><span class="p">,</span> <span class="s2">&#34;s&#34;</span><span class="p">,</span> <span class="s2">&#34;f&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;after&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;%&#34;</span><span class="p">,</span> <span class="s2">&#34;x&#34;</span><span class="p">,</span> <span class="s2">&#34;`&#34;</span><span class="p">,</span> <span class="s2">&#34;`&#34;</span><span class="p">,</span> <span class="s2">&#34;d&#34;</span><span class="p">,</span> <span class="s2">&#34;f&#34;</span><span class="p">,</span> <span class="s2">&#34;(&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>In my mind <code>dsf</code> -&gt; <code>delete surrounding function</code> and <code>yaf</code> -&gt; <code>yank a function</code>.</p>
<h2 id="easymotion">Easymotion</h2>
<p>Easy motion is a great plugin for vim which makes it much easier to move around the document.
I have <code>&lt;SPACE&gt; + a</code> shortcut setup with a slightly more complicated easy motion shortcut
which will allow us to move anywhere in the document.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;vim.easymotion&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;vim.leader&#34;</span><span class="p">:</span> <span class="s2">&#34;&lt;Space&gt;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;vim.easymotionMarkerBackgroundColor&#34;</span><span class="p">:</span> <span class="s2">&#34;#7e57c2&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;vim.normalModeKeyBindingsNonRecursive&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;before&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;&lt;Space&gt;&#34;</span><span class="p">,</span> <span class="s2">&#34;a&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;after&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;leader&#34;</span><span class="p">,</span> <span class="s2">&#34;leader&#34;</span><span class="p">,</span> <span class="s2">&#34;leader&#34;</span><span class="p">,</span> <span class="s2">&#34;b&#34;</span><span class="p">,</span> <span class="s2">&#34;d&#34;</span><span class="p">,</span> <span class="s2">&#34;w&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="p">],</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p><img
        loading="lazy"
        src="/posts/2022-12-02-my-useful-vim-commands-with-vs-code/images/easymotion.gif"
        type=""
        alt="Easymotion"
        
      /></p>
<h2 id="panel-navigation">Panel Navigation</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">[</span>
</span></span><span class="line"><span class="cl"> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;ctrl+j&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;selectNextSuggestion&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;when&#34;</span><span class="p">:</span> <span class="s2">&#34;editorTextFocus &amp;&amp; suggestWidgetMultipleSuggestions &amp;&amp; suggestWidgetVisible&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;ctrl+k&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;selectPrevSuggestion&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;when&#34;</span><span class="p">:</span> <span class="s2">&#34;editorTextFocus &amp;&amp; suggestWidgetMultipleSuggestions &amp;&amp; suggestWidgetVisible&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span></code></pre></div><p>Allow us to use <code>ctrl+j</code> and <code>ctrl+k</code> to navigate auto-suggestions from vscode rather than using the arrow keys.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">[</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;ctrl+l&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;workbench.action.focusRightGroup&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;ctrl+h&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;workbench.action.focusLeftGroup&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;ctrl+h&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;workbench.action.navigateLeft&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;ctrl+l&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;workbench.action.navigateRight&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;ctrl+k&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;workbench.action.navigateUp&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;ctrl+j&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;workbench.action.navigateDown&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span></code></pre></div><p><img
        loading="lazy"
        src="/posts/2022-12-02-my-useful-vim-commands-with-vs-code/images/navigate.gif"
        type=""
        alt="Navigate Panels"
        
      /></p>
<h2 id="file-explorer">File Explorer</h2>
<p>Used to navigate across vscode using ctrl and the vim replacement arrow keys.
Allow us to again not have to use the mouse we can jump to any section of vscode, i.e. terminal to the editor.</p>
<p><img
        loading="lazy"
        src="/posts/2022-12-02-my-useful-vim-commands-with-vs-code/images/nerd_tree.gif"
        type=""
        alt="Nerd Tree Navigation"
        
      /></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">[</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;enter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;list.select&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;when&#34;</span><span class="p">:</span> <span class="s2">&#34;explorerViewletVisible &amp;&amp; filesExplorerFocus&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;l&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;list.select&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;when&#34;</span><span class="p">:</span> <span class="s2">&#34;explorerViewletVisible &amp;&amp; filesExplorerFocus &amp;&amp; !inputFocus&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;o&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;list.toggleExpand&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;when&#34;</span><span class="p">:</span> <span class="s2">&#34;explorerViewletVisible &amp;&amp; filesExplorerFocus &amp;&amp; !inputFocus&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;h&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;list.collapse&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;when&#34;</span><span class="p">:</span> <span class="s2">&#34;explorerViewletVisible &amp;&amp; filesExplorerFocus &amp;&amp; !inputFocus&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;a&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;explorer.newFile&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;when&#34;</span><span class="p">:</span> <span class="s2">&#34;filesExplorerFocus &amp;&amp; !inputFocus&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;shift+a&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;explorer.newFolder&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;when&#34;</span><span class="p">:</span> <span class="s2">&#34;filesExplorerFocus &amp;&amp; !inputFocus&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;r&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;renameFile&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;when&#34;</span><span class="p">:</span> <span class="s2">&#34;explorerViewletVisible &amp;&amp; filesExplorerFocus &amp;&amp; !explorerResourceIsRoot &amp;&amp; !explorerResourceReadonly &amp;&amp; !inputFocus&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;enter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;-renameFile&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;when&#34;</span><span class="p">:</span> <span class="s2">&#34;explorerViewletVisible &amp;&amp; filesExplorerFocus &amp;&amp; !explorerResourceIsRoot &amp;&amp; !explorerResourceReadonly &amp;&amp; !inputFocus&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span></code></pre></div><h2 id="misc">Misc</h2>
<p>Some other random settings I have all of these are in my <code>settings.json</code> file.</p>
<h3 id="relative-lines">Relative Lines</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;vim.smartRelativeLine&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>In normal mode gives us relative lines to the current lines makes easier moving using vim keys such <code>h j k l</code></p>
<h3 id="back-to-normal-mode">Back to Normal Mode</h3>
<p>This means we can access normal mode by pressing <code>j + j</code> instead of <code>&lt;ESC&gt;</code> key.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="nt">&#34;vim.insertModeKeyBindings&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;before&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;j&#34;</span><span class="p">,</span> <span class="s2">&#34;j&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;after&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;&lt;Esc&gt;&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">],</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p><img
        loading="lazy"
        src="/posts/2022-12-02-my-useful-vim-commands-with-vs-code/images/normal_mode.gif"
        type=""
        alt="Normal Mode"
        
      /></p>
<h3 id="infinite-indent">&ldquo;Infinite&rdquo; Indent</h3>
<p>This allows us to ident and outdent without needing to keep reselecting the visual block.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;vim.visualModeKeyBindings&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;before&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;&gt;&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;after&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;&gt;&#34;</span><span class="p">,</span> <span class="s2">&#34;g&#34;</span><span class="p">,</span> <span class="s2">&#34;v&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;before&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;&lt;&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;after&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;&lt;&#34;</span><span class="p">,</span> <span class="s2">&#34;g&#34;</span><span class="p">,</span> <span class="s2">&#34;v&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p><img
        loading="lazy"
        src="/posts/2022-12-02-my-useful-vim-commands-with-vs-code/images/indent_outdent.gif"
        type=""
        alt="Indent"
        
      /></p>
<h2 id="normal-key-bindings">Normal Key Bindings</h2>
<p>For some other bindings, the <code>g</code> command which opens the which menu you saw above and <code>&lt;SPACE&gt; + w</code> will open a new vertical split.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;vim.normalModeKeyBindingsNonRecursive&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;before&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;g&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;commands&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;whichkey.show&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;before&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;&lt;leader&gt;&#34;</span><span class="p">,</span> <span class="s2">&#34;w&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;commands&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;workbench.action.splitEditor&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Taken from <a href="https://stackoverflow.com/a/10635043/3108619">https://stackoverflow.com/a/10635043/3108619</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Taken from <a href="https://stackoverflow.com/a/3638557/3108619">https://stackoverflow.com/a/3638557/3108619</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>Taken from <a href="https://vi.stackexchange.com/a/2985">https://vi.stackexchange.com/a/2985</a>&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to use copier to create project templates</title>
      <link>https://haseebmajid.dev/posts/2022-12-01-how-to-use-copier-to-create-project-templates/</link>
      <pubDate>Thu, 01 Dec 2022 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2022-12-01-how-to-use-copier-to-create-project-templates/</guid>
      <description>&lt;p&gt;Hi &amp;#x1f44b; everyone, in this blog post I&amp;rsquo;m going to over how we can use &lt;code&gt;Copier&lt;/code&gt; to create templates for our repositories.&lt;/p&gt;
&lt;h2 id=&#34;why-create-a-template-repo-&#34;&gt;Why create a template repo ?&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m sure some of you are wondering why do we even need templates ? Well for a few reasons, one it gives you a consistent way to create
new services from a repository. For my personal project Banter Bus I have a &lt;a href=&#34;https://gitlab.com/banter-bus/fastapi-template&#34;&gt;FastAPI Template&lt;/a&gt;,
which I use to create new FastAPI services from. It creates a lot of boilerplate which I don&amp;rsquo;t have to worry about. It provides consistency
so makes it easier to jump between my FastAPI services in Banter Bus.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Hi &#x1f44b; everyone, in this blog post I&rsquo;m going to over how we can use <code>Copier</code> to create templates for our repositories.</p>
<h2 id="why-create-a-template-repo-">Why create a template repo ?</h2>
<p>I&rsquo;m sure some of you are wondering why do we even need templates ? Well for a few reasons, one it gives you a consistent way to create
new services from a repository. For my personal project Banter Bus I have a <a href="https://gitlab.com/banter-bus/fastapi-template">FastAPI Template</a>,
which I use to create new FastAPI services from. It creates a lot of boilerplate which I don&rsquo;t have to worry about. It provides consistency
so makes it easier to jump between my FastAPI services in Banter Bus.</p>
<p>All I have to do is render the template, using the copier CLI tool fill in some values such as service name. Then I have a project
skeleton that I can start with.</p>
<h2 id="why-not-cookiecutter-">Why not cookiecutter ?</h2>
<p>Some of you may also be asking why not use cookiecutter instead of copier. For one simple reason copier allows us to update
downstream services that were rendered from this template. So in theory we can update a file in the template, say a make target,
and pull in this change from all the projects that were rendered from this template. I will go over how to do this in another post.
But just keep in mind this is possible, you can <a href="https://copier.readthedocs.io/en/stable/updating/">read more here</a>.</p>
<p>You can read more about the comparison in the <a href="https://copier.readthedocs.io/en/stable/comparisons/">copier docs here</a>.</p>
<h2 id="lets-get-started">Let&rsquo;s get started</h2>
<details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">Existing Repository</summary>
  
  Probably the easiest way to create a template repository is to take an existing one and copy it.
Then we template out the copied repo. In my case I called it FastAPI Template and created a new
project on Gitlab.
</details>

<p>Now that we have a repository, we can start to template it.
First create a file in the root of your project called <code>copier.yaml</code>. The file will be used to prompt the user for answers to these questions.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">service_name</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">str</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">help</span><span class="p">:</span><span class="w"> </span><span class="l">Name of your project (lowercase, hyphens like &#39;banter-bus-api&#39;)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">default</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">service_title</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">str</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">help</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Title of your project (title case like &#39;Banter Bus API&#39;)&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">default</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;{{service_name | title | replace(&#39;-&#39;, &#39; &#39;)}}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">service_prefix</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">str</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">help</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Prefix of environment variables for this service (upper case like &#39;BANTER_BUS_API&#39;)&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">default</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;{{service_name | upper | replace(&#39;-&#39;, &#39;_&#39;)}}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">database_name</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">str</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">help</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Your database name (lowercase, hypens replaced with underscore and no banter-bus like &#39;api&#39;)&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">default</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;{{service_name | replace(&#39;-&#39;, &#39;_&#39;) | replace(&#39;banter_bus_&#39;, &#39;&#39;)}}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">short_description</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">str</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">help</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;A short description of the project&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">include_ci</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">bool</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">help</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Whether to include a Gitlab CI file&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">default</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">_exclude</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">copier.yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">__pycache__</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">.git</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">CHANGELOG.md</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">README.md</span><span class="w">
</span></span></span></code></pre></div><p>We can transform our variables, for example we can remove <code>-</code> and replacing them with spaces. We can use normal jinja syntax.</p>
<p>The <code>_exclude</code>, field it used to not template certain files these files don&rsquo;t get copied over.
By default all the files get copied over and any file ending in <code>.jinja</code> is templated.</p>
<p>Next create a file at the root called <code>{{ _copier_conf.answers_file }}.jinja</code> and copy the following</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jinja" data-lang="jinja"><span class="line"><span class="cl"><span class="cp">{{</span> <span class="nv">_copier_conf.answers_file</span> <span class="cp">}}</span><span class="x">.jinja
</span></span></span></code></pre></div><h3 id="templating">Templating</h3>
<p>Now we have those two files we can start to template out the project. All we need to do is append <code>.jinja</code> to any file we want to template.
So for example imagine we had a <code>docker-compose.yml</code> -&gt; <code>docker-compose.yml.jinja</code>, we could do something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">api</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span>{{<span class="l">service_name}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">build</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">context</span><span class="p">:</span><span class="w"> </span><span class="l">.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">dockerfile</span><span class="p">:</span><span class="w"> </span><span class="l">Dockerfile</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">target</span><span class="p">:</span><span class="w"> </span><span class="l">development</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">cache_from</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">registry.gitlab.com/banter-bus/{{service_name}}:development</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">XDG_DATA_HOME</span><span class="p">:</span><span class="w"> </span><span class="l">/app/home/commandhistory</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>{{<span class="nt">service_prefix}}_DB_USERNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>{{<span class="nt">service_prefix}}_DB_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>{{<span class="nt">service_prefix}}_DB_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>{{<span class="nt">service_prefix}}_DB_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">27017</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>{{<span class="nt">service_prefix}}_DB_NAME</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>{{<span class="nt">service_prefix}}_CLIENT_ID</span><span class="p">:</span><span class="w"> </span><span class="l">client_id</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>{{<span class="nt">service_prefix}}_USE_AUTH</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;False&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>{{<span class="nt">service_prefix}}_WEB_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">8080</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>{{<span class="nt">service_prefix}}_WEB_HOST</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;0.0.0.0&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">127.0.0.1</span><span class="p">:</span><span class="m">8080</span><span class="p">:</span><span class="m">8080</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./:/app</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/app/.venv/</span><span class="w"> </span><span class="c"># This stops local .venv getting mounted</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">app-history:/app/home/commandhistory</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c"># ....</span><span class="w">
</span></span></span></code></pre></div><p>The templated files can use jinja syntax like so <code>{{service_prefix}}</code>, will be filled using the value provided by the user.
See the <code>copier.yaml</code> file. We have all the functions and filter available from jinja2-ansible-filters. You can <a href="https://copier.readthedocs.io/en/stable/creating/#template-helpers">read more here</a>.</p>
<h2 id="optional-directories">Optional Directories</h2>
<p>We can also use jinja syntax to create optional directories/files if we name a file like <code>{% if include_ci %}.gitlab-ci.yml{% endif %}.jinja</code>.
Then you can populate the file like a normal file such as:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">docker</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">docker:dind</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">DOCKER_DRIVER</span><span class="p">:</span><span class="w"> </span><span class="l">overlay2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">DOCKER_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">tcp://docker:2375</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">pre</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">before_script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">docker compose build</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">publish:docker</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">pre</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">echo ${{service_prefix}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">docker login -u ${CI_REGISTRY_USER} -p ${CI_REGISTRY_PASSWORD} ${CI_REGISTRY}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">docker build --target production -t ${CI_REGISTRY_IMAGE}:latest .</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">docker push ${CI_REGISTRY_IMAGE}:latest</span><span class="w">
</span></span></span></code></pre></div><h2 id="how-to-create-a-service-from-the-template">How to create a service from the template</h2>
<p>Now that we have a template we can generate a project from it, either we can do it locally</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Install copier</span>
</span></span><span class="line"><span class="cl">pipx install copier
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Either run it with a local copy</span>
</span></span><span class="line"><span class="cl">copier path/to/project/template path/to/destination
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># My preference use the git URL</span>
</span></span><span class="line"><span class="cl">copier https://gitlab.com/banter-bus/fastapi-template /path/to/destination
</span></span></code></pre></div><p>But I think the better way to generate it is to use the git URL. In the generated project we have a file
called <code>.copier-answers.yaml</code> which should have a line like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">_src_path</span><span class="p">:</span><span class="w"> </span><span class="l">gl:banter-bus/fastapi-template</span><span class="w">
</span></span></span></code></pre></div><p>This is useful later because we can use <code>copier update</code> to pull in changes, using git, from the upstream template.
But we can only do this we the <code>_src_path</code> is a git URL i.e. <code>gh</code> for GitHub and <code>gl</code> for Gitlab. I will go over more in-depth
in a future post about how to use <code>copier update</code> to update downstream projects.</p>
<p>That&rsquo;s it! We managed to create a new template repository and a new project from a template repository.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/banter-bus/fastapi-template">FastAPI Copier Template</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How you can Verify Your Hugo Blog on Mastodon</title>
      <link>https://haseebmajid.dev/posts/2022-11-30-til-how-you-can-verify-your-hugo-blog-on-mastodon/</link>
      <pubDate>Wed, 30 Nov 2022 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2022-11-30-til-how-you-can-verify-your-hugo-blog-on-mastodon/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How you can Verify Your Hugo Blog on Mastodon&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Mastodon is an open-source social media website gaining a lot of traction at the moment, thanks in part to what is happening at Twitter.
On Mastodon, we can verify a link on our profile is owned by us by linking back to Mastodon with a &lt;code&gt;rel=&amp;quot;me&amp;quot;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2022-11-30-til-how-you-can-verify-your-hugo-blog-on-mastodon/images/mastodon_settings.png&#34;
        type=&#34;&#34;
        alt=&#34;Settings&#34;
        
      /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2022-11-30-til-how-you-can-verify-your-hugo-blog-on-mastodon/images/verification.png&#34;
        type=&#34;&#34;
        alt=&#34;Verification&#34;
        
      /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;ol&gt;
&lt;li&gt;Go to settings/profile&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;In my case, it is this, &lt;a href=&#34;https://hachyderm.io/settings/profile&#34;&gt;https://hachyderm.io/settings/profile&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;ol&gt;
&lt;li&gt;Find the verification link&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;It will look something like &lt;code&gt;&amp;lt;a rel=&amp;quot;me&amp;quot; href=&amp;quot;https://hachyderm.io/@majiy00&amp;quot;&amp;gt;Mastodon&amp;lt;/a&amp;gt;&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Yours may look different if you are on a different Mastodon instance&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Copy this into our site
&lt;ul&gt;
&lt;li&gt;For example, in your footer &lt;code&gt;footer.html&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go-html-template&#34; data-lang=&#34;go-html-template&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;span&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;style&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;display: inline-block; margin-left: 1em;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Powered by
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;a&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;href&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;https://gohugo.io/&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;rel&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;noopener noreferrer&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;target&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;_blank&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;Hugo&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;err&#34;&gt;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;a&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;href&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;https://github.com/reorx/hugo-PaperModX/&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;rel&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;noopener&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;target&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;_blank&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;PaperModX&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line hl&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;a&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;rel&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;me&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;href&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;https://hachyderm.io/@majiy00&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;Mastodon&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;cp&#34;&gt;{{-&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;na&#34;&gt;.Site.Params.goatcounter&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;cp&#34;&gt;}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;span&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        Analytics by &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;a&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;href&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;https://&lt;/span&gt;&lt;span class=&#34;cp&#34;&gt;{{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;na&#34;&gt;.Site.Params.goatcounter&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;cp&#34;&gt;}}&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;.goatcounter.com&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;Goatcounter&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;span&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;cp&#34;&gt;{{-&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;end&lt;/span&gt;&lt;span class=&#34;cp&#34;&gt;}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;span&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;papermod&#34;&gt;PaperMod&lt;/h2&gt;
&lt;p&gt;If you are using the &lt;a href=&#34;https://adityatelange.github.io/hugo-PaperMod/&#34;&gt;PaperMod&lt;/a&gt; theme or similar.
You can make this change to the &lt;code&gt;config.yml&lt;/code&gt;:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How you can Verify Your Hugo Blog on Mastodon</strong></p>
<p>Mastodon is an open-source social media website gaining a lot of traction at the moment, thanks in part to what is happening at Twitter.
On Mastodon, we can verify a link on our profile is owned by us by linking back to Mastodon with a <code>rel=&quot;me&quot;</code>.</p>
<p><img
        loading="lazy"
        src="/posts/2022-11-30-til-how-you-can-verify-your-hugo-blog-on-mastodon/images/mastodon_settings.png"
        type=""
        alt="Settings"
        
      /></p>
<p><img
        loading="lazy"
        src="/posts/2022-11-30-til-how-you-can-verify-your-hugo-blog-on-mastodon/images/verification.png"
        type=""
        alt="Verification"
        
      /></p>
<ul>
<li>
<ol>
<li>Go to settings/profile</li>
</ol>
<ul>
<li>In my case, it is this, <a href="https://hachyderm.io/settings/profile">https://hachyderm.io/settings/profile</a></li>
</ul>
</li>
<li>
<ol>
<li>Find the verification link</li>
</ol>
<ul>
<li>It will look something like <code>&lt;a rel=&quot;me&quot; href=&quot;https://hachyderm.io/@majiy00&quot;&gt;Mastodon&lt;/a&gt;</code>
<ul>
<li>Yours may look different if you are on a different Mastodon instance</li>
</ul>
</li>
</ul>
</li>
<li>Copy this into our site
<ul>
<li>For example, in your footer <code>footer.html</code></li>
</ul>
</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go-html-template" data-lang="go-html-template"><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">span</span> <span class="na">style</span><span class="o">=</span><span class="s">&#34;display: inline-block; margin-left: 1em;&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    Powered by
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;https://gohugo.io/&#34;</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;noopener noreferrer&#34;</span> <span class="na">target</span><span class="o">=</span><span class="s">&#34;_blank&#34;</span><span class="p">&gt;</span>Hugo<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span> <span class="err">&amp;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;https://github.com/reorx/hugo-PaperModX/&#34;</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;noopener&#34;</span> <span class="na">target</span><span class="o">=</span><span class="s">&#34;_blank&#34;</span><span class="p">&gt;</span>PaperModX<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span>.
</span></span><span class="line hl"><span class="cl">    <span class="p">&lt;</span><span class="nt">a</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;me&#34;</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;https://hachyderm.io/@majiy00&#34;</span><span class="p">&gt;</span>Mastodon<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="cp">{{-</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="na">.Site.Params.goatcounter</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">span</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        Analytics by <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;https://</span><span class="cp">{{</span><span class="w"> </span><span class="na">.Site.Params.goatcounter</span><span class="w"> </span><span class="cp">}}</span><span class="s">.goatcounter.com&#34;</span><span class="p">&gt;</span>Goatcounter<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span>.
</span></span><span class="line"><span class="cl">      <span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="cp">{{-</span><span class="w"> </span><span class="k">end</span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span>
</span></span></code></pre></div><h2 id="papermod">PaperMod</h2>
<p>If you are using the <a href="https://adityatelange.github.io/hugo-PaperMod/">PaperMod</a> theme or similar.
You can make this change to the <code>config.yml</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">params</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">socialIcons</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">gitlab</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">url</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;https://gitlab.com/hmajid2301&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">github</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">url</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;https://github.com/hmajid2301&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">linkedin</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">url</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;https://www.linkedin.com/in/haseeb-majid-ba0a5194/&#34;</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">mastodon</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">      </span><span class="nt">url</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;https://hachyderm.io/@majiy00&#34;</span><span class="w">
</span></span></span></code></pre></div><p>If you drill down into the code you will see these get turned into anchors tags in <code>social_icons.html</code> page, like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go-html-template" data-lang="go-html-template"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;</span><span class="cp">{{</span><span class="w"> </span><span class="nx">trim</span><span class="w"> </span><span class="na">.url</span><span class="w"> </span><span class="s">&#34; &#34;</span><span class="w"> </span><span class="cp">}}</span><span class="s">&#34;</span> <span class="na">target</span><span class="o">=</span><span class="s">&#34;_blank&#34;</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;noopener noreferrer me&#34;</span> <span class="na">title</span><span class="o">=</span><span class="s">&#34;</span><span class="cp">{{</span><span class="w"> </span><span class="na">.name</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">title</span><span class="w"> </span><span class="cp">}}</span><span class="s">&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="cp">{{</span><span class="w"> </span><span class="nx">safeHTML</span><span class="w"> </span><span class="o">(</span><span class="k">index</span><span class="w"> </span><span class="na">$.svg</span><span class="w"> </span><span class="nx">$icon_name</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">default</span><span class="w"> </span><span class="na">$.svg.default</span><span class="o">)</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span>
</span></span></code></pre></div>]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Fix &#34;Overflowing&#34; Highlights in Hugo Code Blocks</title>
      <link>https://haseebmajid.dev/posts/2022-11-27-til-how-to-fix-overflowing-highlights-in-hugo-code-blocks/</link>
      <pubDate>Sun, 27 Nov 2022 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2022-11-27-til-how-to-fix-overflowing-highlights-in-hugo-code-blocks/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Fix &amp;ldquo;Overflowing&amp;rdquo; Highlights in Hugo Code Blocks&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I am using the &lt;a href=&#34;https://reorx.github.io/hugo-PaperModX/&#34;&gt;PaperModX&lt;/a&gt; theme, or at least a theme that is very close to that theme.
I noticed an issue when I would try to highlight my code in code blocks. For example, if we had the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-markdown&#34; data-lang=&#34;markdown&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;```js {hl_lines=[1]}
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;const images = Array.from(document.querySelectorAll(&amp;#34;.post-content img&amp;#34;));
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;\```
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It would highlight line number the first line in the code block but just up to the width of the code block.
So if you scrolled to the right to see the rest of the code it would not be highlighted.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Fix &ldquo;Overflowing&rdquo; Highlights in Hugo Code Blocks</strong></p>
<p>I am using the <a href="https://reorx.github.io/hugo-PaperModX/">PaperModX</a> theme, or at least a theme that is very close to that theme.
I noticed an issue when I would try to highlight my code in code blocks. For example, if we had the following:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">```js {hl_lines=[1]}
</span></span><span class="line"><span class="cl">const images = Array.from(document.querySelectorAll(&#34;.post-content img&#34;));
</span></span><span class="line"><span class="cl">\```
</span></span></code></pre></div><p>It would highlight line number the first line in the code block but just up to the width of the code block.
So if you scrolled to the right to see the rest of the code it would not be highlighted.</p>
<p><img
        loading="lazy"
        src="/posts/2022-11-27-til-how-to-fix-overflowing-highlights-in-hugo-code-blocks/images/hugo_code_blocks.png"
        type=""
        alt="Hugo Code Blocks Highlighting"
        
      /></p>
<p>This doesn&rsquo;t look great so let&rsquo;s fix it, find your CSS code that does the styling for the code blocks.
In my case it is called <code>assets/css/lib/chroma-dark.css</code> and <code>assets/css/lib/chroma-light.css</code>.</p>
<p>Then in the <code>LineHighlight</code> part add <code>width: max-content;</code> so my light theme files looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="c">/* LineHighlight */</span> <span class="p">.</span><span class="nc">chroma</span> <span class="p">.</span><span class="nc">hl</span> <span class="p">{</span> <span class="k">background-color</span><span class="p">:</span> <span class="mh">#ffffcc</span><span class="p">;</span> <span class="k">width</span><span class="p">:</span> <span class="n">max-content</span><span class="p">;</span> <span class="p">}</span>
</span></span></code></pre></div><p>and my dark theme file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="c">/* LineHighlight */</span> <span class="p">.</span><span class="nc">chroma</span> <span class="p">.</span><span class="nc">hl</span> <span class="p">{</span> <span class="k">background-color</span><span class="p">:</span> <span class="mh">#bd93f9</span><span class="mi">40</span><span class="p">;</span> <span class="k">width</span><span class="p">:</span> <span class="n">max-content</span><span class="p">;</span> <span class="p">}</span>
</span></span></code></pre></div><p>Now it looks like this:</p>
<p><img
        loading="lazy"
        src="/posts/2022-11-27-til-how-to-fix-overflowing-highlights-in-hugo-code-blocks/images/hugo_code_blocks_fixed.png"
        type=""
        alt="Hugo Code Blocks Highlighting Fixed"
        
      /></p>
<p>That&rsquo;s it!</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How to Add Hugo Shortcode Snippets in VSCode</title>
      <link>https://haseebmajid.dev/posts/2022-11-26-til-how-to-add-hugo-shortcode-snippets-in-vscode/</link>
      <pubDate>Sat, 26 Nov 2022 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2022-11-26-til-how-to-add-hugo-shortcode-snippets-in-vscode/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Add Hugo Shortcode Snippets in VSCode&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Hugo &lt;a href=&#34;https://gohugo.io/content-management/shortcodes/&#34;&gt;shortcodes&lt;/a&gt; are a great way to add functionality to our Hugo blog.
However, I find them fiddly to add.&lt;/p&gt;
&lt;p&gt;So let&amp;rsquo;s see how we can leverage VSCode snippets to make it easier to add shortcodes to our markdown files.
First, open the command palette, then select &lt;code&gt;Snippets: Configure User Snippets&lt;/code&gt;. In my case, I want to add snippets
specific to this project so I select &lt;code&gt;New snippets file for &#39;&amp;lt;project&amp;gt;&#39;&lt;/code&gt;. Give it a name like &lt;code&gt;shortcodes&lt;/code&gt;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Add Hugo Shortcode Snippets in VSCode</strong></p>
<p>Hugo <a href="https://gohugo.io/content-management/shortcodes/">shortcodes</a> are a great way to add functionality to our Hugo blog.
However, I find them fiddly to add.</p>
<p>So let&rsquo;s see how we can leverage VSCode snippets to make it easier to add shortcodes to our markdown files.
First, open the command palette, then select <code>Snippets: Configure User Snippets</code>. In my case, I want to add snippets
specific to this project so I select <code>New snippets file for '&lt;project&gt;'</code>. Give it a name like <code>shortcodes</code>.</p>
<p>This will create a new file in <code>.vscode/shortcodes.code-snippets</code>.
Then I will update the file to make it look like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;invidious&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;prefix&#34;</span><span class="p">:</span> <span class="s2">&#34;invidious&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;body&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;{{&lt; invidious ${1:link} &gt;}}&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;description&#34;</span><span class="p">:</span> <span class="s2">&#34;invidious Hugo shortcode&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;notice&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;prefix&#34;</span><span class="p">:</span> <span class="s2">&#34;notice&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;body&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;{{&lt; notice type=\&#34;${1:type}\&#34; title=\&#34;${3:title}\&#34; &gt;}}&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;${4:content}&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;{{&lt; notice &gt;}}&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;$0&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;description&#34;</span><span class="p">:</span> <span class="s2">&#34;notice Hugo shortcode&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Which could then easily be turned into this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl">{{<span class="p">&lt;</span> <span class="nt">notice</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;warning&#34;</span> <span class="na">title</span><span class="o">=</span><span class="s">&#34;warning&#34;</span> <span class="p">&gt;</span>}}
</span></span><span class="line"><span class="cl">This is a warning
</span></span><span class="line"><span class="cl">{{<span class="p">&lt;</span> <span class="p">/</span><span class="nt">notice</span> <span class="p">&gt;</span>}}
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">{{<span class="p">&lt;</span> <span class="nt">invidious</span> <span class="na">rALo_BzGKs8</span> <span class="p">&gt;</span>}}
</span></span></code></pre></div><p>To add them to our markdown files we just need to type the prefix i.e. <code>notice</code> and then press the auto-complete button i.e. <code>tab</code>.
However, we may need to turn on autocomplete for markdown files in our <code>settings.json</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;[markdown]&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;editor.quickSuggestions&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;other&#34;</span><span class="p">:</span> <span class="s2">&#34;on&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;comments&#34;</span><span class="p">:</span> <span class="s2">&#34;off&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;strings&#34;</span><span class="p">:</span> <span class="s2">&#34;off&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>🙌 That&rsquo;s it! You should be able to use the code snippets more easily add shortcodes!</p>
<h2 id="optional-extensions">(Optional) Extensions</h2>
<p>I also use the following extensions with VSCode (specifically for Hugo):</p>
<ul>
<li><a href="https://marketplace.visualstudio.com/items?itemName=budparr.language-hugo-vscode"><code>Hugo Language and Syntax Support</code></a>: Adds snippets for builtin Hugo shortcodes i.e. <code>highlight</code></li>
<li><a href="https://marketplace.visualstudio.com/items?itemName=kaellarkin.hugo-shortcode-syntax"><code>Hugo Shortcode Syntax Highlighting</code></a>: Adds syntax highlighting for shortcodes in markdown files</li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Add Page Views to your Hugo Blog Posts Using Goatcounter</title>
      <link>https://haseebmajid.dev/posts/2022-11-25-how-to-add-page-views-to-your-hugo-blog-posts-using-goatcounter/</link>
      <pubDate>Fri, 25 Nov 2022 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2022-11-25-how-to-add-page-views-to-your-hugo-blog-posts-using-goatcounter/</guid>
      <description>&lt;p&gt;In this post, we will go over how we can add a page view &amp;ldquo;counter&amp;rdquo; 👀 to our Hugo blog, so we can see how many views each of our posts
have had. We will do this using &lt;a href=&#34;https://www.goatcounter.com/&#34;&gt;Goatcounter Analytics&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here is an example of what it may look like:&lt;/p&gt;
&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2022-11-25-how-to-add-page-views-to-your-hugo-blog-posts-using-goatcounter/images/page_views.png&#34;
        type=&#34;&#34;
        alt=&#34;Page Views Example&#34;
        
      /&gt;&lt;/p&gt;
&lt;details
  class=&#34;notice warning&#34;
  open=&#34;true&#34;
&gt;
    &lt;summary class=&#34;notice-title&#34;&gt;Goatcounter Set Up&lt;/summary&gt;
  
  This post assumes you have already created a Goatcounter account, &lt;a href=&#34;https://www.goatcounter.com/&#34;&gt;more information here&lt;/a&gt;.
And you have added Goatcounter analytics to your blog, &lt;a href=&#34;https://haseebmajid.dev/posts/2022-11-20-til-how-you-can-add-goatcounter-to-your-hugo-blog/&#34;&gt;see this post for more information&lt;/a&gt;
&lt;/details&gt;

&lt;h2 id=&#34;page-views-partial&#34;&gt;Page Views Partial&lt;/h2&gt;
&lt;p&gt;First, create a new file in your partial folder, in my case, it will be &lt;code&gt;layouts/partials/page_views.html&lt;/code&gt;&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this post, we will go over how we can add a page view &ldquo;counter&rdquo; 👀 to our Hugo blog, so we can see how many views each of our posts
have had. We will do this using <a href="https://www.goatcounter.com/">Goatcounter Analytics</a>.</p>
<p>Here is an example of what it may look like:</p>
<p><img
        loading="lazy"
        src="/posts/2022-11-25-how-to-add-page-views-to-your-hugo-blog-posts-using-goatcounter/images/page_views.png"
        type=""
        alt="Page Views Example"
        
      /></p>
<details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">Goatcounter Set Up</summary>
  
  This post assumes you have already created a Goatcounter account, <a href="https://www.goatcounter.com/">more information here</a>.
And you have added Goatcounter analytics to your blog, <a href="/posts/2022-11-20-til-how-you-can-add-goatcounter-to-your-hugo-blog/">see this post for more information</a>
</details>

<h2 id="page-views-partial">Page Views Partial</h2>
<p>First, create a new file in your partial folder, in my case, it will be <code>layouts/partials/page_views.html</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go-html-template" data-lang="go-html-template"><span class="line hl"><span class="cl"><span class="p">&lt;</span><span class="nt">span</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;</span><span class="cp">{{</span><span class="w"> </span><span class="na">.File.UniqueID</span><span class="w"> </span><span class="cp">}}</span><span class="s">&#34;</span> <span class="na">title</span><span class="o">=</span><span class="s">&#34;</span><span class="cp">{{</span><span class="w"> </span><span class="nx">i18n</span><span class="w"> </span><span class="s">&#34;article.page_views&#34;</span><span class="w"> </span><span class="cp">}}</span><span class="s">&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">var</span> <span class="nx">r</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">XMLHttpRequest</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="nx">r</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;load&#39;</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s1">&#39;</span><span class="cp">{{</span><span class="w"> </span><span class="na">.File.UniqueID</span><span class="w"> </span><span class="cp">}}</span><span class="s1">&#39;</span><span class="p">).</span><span class="nx">innerText</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">responseText</span><span class="p">).</span><span class="nx">count_unique</span> <span class="o">+</span> <span class="s1">&#39; &#39;</span> <span class="o">+</span> <span class="cp">{{</span><span class="w"> </span><span class="nx">i18n</span><span class="w"> </span><span class="s">&#34;article.page_views&#34;</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="nx">r</span><span class="p">.</span><span class="nx">open</span><span class="p">(</span><span class="s1">&#39;GET&#39;</span><span class="p">,</span> <span class="s2">&#34;https://</span><span class="cp">{{</span><span class="w"> </span><span class="na">.Site.Params.goatcounter</span><span class="w"> </span><span class="cp">}}</span><span class="s2">.goatcounter.com/counter/&#34;</span> <span class="o">+</span> <span class="nb">encodeURIComponent</span><span class="p">(</span><span class="cp">{{</span><span class="w"> </span><span class="na">.RelPermalink</span><span class="w"> </span><span class="cp">}}</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/(\/)?$/</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="p">))</span> <span class="o">+</span> <span class="s1">&#39;.json&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nx">r</span><span class="p">.</span><span class="nx">send</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>We use <code>{{ .File.UniqueID }}</code> as the id because we may want to display the page views on a page with other articles.
This here will return an MD5 hash of the page. So this will be unique for each blog post/page on your Hugo blog.</p>
<p>We then send an HTTP request to the Goatcounter site for example <code>https://haseebmajid.goatcounter.com/counter/%2Fposts%2F2022-12-15-how-to-use-dotbot-to-personalise-your-vscode-devcontainers.json</code>.
This will return all the page views for the specific page at <code>/posts/2022-12-15-how-to-use-dotbot-to-personalise-your-vscode-devcontainers/</code>.</p>
<p>Because I am using the PaperMod theme I adjusted the above HTML slightly. Which adds an emoji and a space between page views and the emoji.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go-html-template" data-lang="go-html-template"><span class="line hl"><span class="cl"><span class="p">&lt;</span><span class="nt">span</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;meta-item&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">svg</span> <span class="na">xmlns</span><span class="o">=</span><span class="s">&#34;http://www.w3.org/2000/svg&#34;</span> <span class="na">width</span><span class="o">=</span><span class="s">&#34;24&#34;</span> <span class="na">height</span><span class="o">=</span><span class="s">&#34;24&#34;</span> <span class="na">viewBox</span><span class="o">=</span><span class="s">&#34;0 0 24 24&#34;</span> <span class="na">fill</span><span class="o">=</span><span class="s">&#34;none&#34;</span> <span class="na">stroke</span><span class="o">=</span><span class="s">&#34;currentColor&#34;</span> <span class="na">stroke-width</span><span class="o">=</span><span class="s">&#34;2&#34;</span> <span class="na">stroke-linecap</span><span class="o">=</span><span class="s">&#34;round&#34;</span> <span class="na">stroke-linejoin</span><span class="o">=</span><span class="s">&#34;round&#34;</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;feather feather-activity&#34;</span><span class="p">&gt;&lt;</span><span class="nt">path</span> <span class="na">d</span><span class="o">=</span><span class="s">&#34;M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">path</span><span class="p">&gt;&lt;</span><span class="nt">circle</span> <span class="na">cx</span><span class="o">=</span><span class="s">&#34;12&#34;</span> <span class="na">cy</span><span class="o">=</span><span class="s">&#34;12&#34;</span> <span class="na">r</span><span class="o">=</span><span class="s">&#34;3&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">circle</span><span class="p">&gt;&lt;/</span><span class="nt">svg</span><span class="p">&gt;</span> 
</span></span><span class="line"><span class="cl">    <span class="ni">&amp;nbsp;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">span</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;</span><span class="cp">{{</span><span class="w"> </span><span class="na">.File.UniqueID</span><span class="w"> </span><span class="cp">}}</span><span class="s">&#34;</span> <span class="na">title</span><span class="o">=</span><span class="s">&#34;</span><span class="cp">{{</span><span class="w"> </span><span class="nx">i18n</span><span class="w"> </span><span class="s">&#34;article.page_views&#34;</span><span class="w"> </span><span class="cp">}}</span><span class="s">&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span>
</span></span></code></pre></div><h2 id="i18n">i18n</h2>
<p>Let&rsquo;s go to our <code>i18n</code> files and add the following say to <code>i18n/en.yaml</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">article</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">page_views</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Views&#34;</span><span class="w">
</span></span></span></code></pre></div><h2 id="layouts">Layouts</h2>
<p>Finally find where you actually want to show the page views, in my case I just want to show them in the post themselves and no where else.
So I will go to <code>layouts/_default/single.html</code> and add the following.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go-html-template" data-lang="go-html-template"><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">header</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;post-header&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="cp">{{</span><span class="w"> </span><span class="nx">partial</span><span class="w"> </span><span class="s">&#34;breadcrumbs.html&#34;</span><span class="w"> </span><span class="na">.</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">h1</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;post-title&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="cp">{{-</span><span class="w"> </span><span class="na">.Title</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl">      <span class="cp">{{-</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="na">.Draft</span><span class="w"> </span><span class="cp">-}}</span><span class="p">&lt;</span><span class="nt">sup</span><span class="p">&gt;&lt;</span><span class="nt">span</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;entry-isdraft&#34;</span><span class="p">&gt;</span><span class="ni">&amp;nbsp;&amp;nbsp;</span>[draft]<span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;&lt;/</span><span class="nt">sup</span><span class="p">&gt;</span><span class="cp">{{-</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">h1</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="cp">{{-</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="na">.Description</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;post-description&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="cp">{{-</span><span class="w"> </span><span class="na">.Description</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="cp">{{-</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl">    <span class="cp">{{-</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="o">(</span><span class="na">.Param</span><span class="w"> </span><span class="s">&#34;hideMeta&#34;</span><span class="o">)</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;post-meta&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="cp">{{-</span><span class="w"> </span><span class="nx">partial</span><span class="w"> </span><span class="s">&#34;post_meta.html&#34;</span><span class="w"> </span><span class="na">.</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl">      <span class="cm">{{/* TODO move to footer */}}</span>
</span></span><span class="line"><span class="cl">      <span class="cp">{{-</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="na">.Params.ShowLikes</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">default</span><span class="w"> </span><span class="nx">site</span><span class="na">.Params.ShowLikes</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">default</span><span class="w"> </span><span class="k">false</span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl">      <span class="cp">{{-</span><span class="w"> </span><span class="nx">partial</span><span class="w"> </span><span class="s">&#34;likes.html&#34;</span><span class="w"> </span><span class="na">.</span><span class="w"> </span><span class="cp">}}</span> 
</span></span><span class="line"><span class="cl">      <span class="cp">{{-</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line hl"><span class="cl">      <span class="cp">{{</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="k">and</span><span class="w"> </span><span class="o">(</span><span class="na">.Params.ShowPageViews</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">default</span><span class="w"> </span><span class="o">(</span><span class="na">.Site.Params.ShowPageViews</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">default</span><span class="w"> </span><span class="k">true</span><span class="o">))</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line hl"><span class="cl">        <span class="cp">{{-</span><span class="w"> </span><span class="nx">partial</span><span class="w"> </span><span class="s">&#34;page_views.html&#34;</span><span class="w"> </span><span class="na">.</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line hl"><span class="cl">      <span class="cp">{{</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="cp">{{</span><span class="w"> </span><span class="nx">partial</span><span class="w"> </span><span class="s">&#34;edit_post.html&#34;</span><span class="w"> </span><span class="na">.</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl">      <span class="cp">{{</span><span class="w"> </span><span class="nx">partial</span><span class="w"> </span><span class="s">&#34;post_canonical.html&#34;</span><span class="w"> </span><span class="na">.</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="cp">{{-</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">header</span><span class="p">&gt;</span>
</span></span></code></pre></div><details
  class="notice tip"
  open="true"
>
    <summary class="notice-title">Site vs Post Params</summary>
  
  Note we check for both site params to see if we should show page views <code>.Site.Params.ShowPageViews</code>.
Or we check if we have turned it off for a specific post <code>.Params.ShowPageViews</code> (in the frontmatter).
</details>

<p>That&rsquo;s it you should be able to have page views on your blog now 🙈!</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://bortox.it/en/article/add-page-views-hugo-goatcounter/">Inspired by this post</a></li>
<li><a href="https://github.com/hmajid2301/hugo-PaperModX/blob/3a2936ef355830df7bbbaf391a3fa28bf0c85ead/layouts/partials/page_views.html">Example</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: You Can Schedule Posts on Your Hugo Blog using Netlify and Gitlab CI</title>
      <link>https://haseebmajid.dev/posts/2022-11-23-til-you-can-schedule-posts-on-your-hugo-blog-using-netlify-and-gitlab-ci/</link>
      <pubDate>Wed, 23 Nov 2022 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2022-11-23-til-you-can-schedule-posts-on-your-hugo-blog-using-netlify-and-gitlab-ci/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: You Can Schedule Posts on Your Hugo Blog using Netlify and Gitlab CI&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This post in fact will be a scheduled post I&amp;rsquo;m writing it on the 20th of November but it will be published on the 23rd of November.
Assuming we are using Netlify to deploy our website, what we will need to do is trigger a rebuild of our site every day using Gitlab CI.
Since Hugo builds a static site to publish our content after a set date we will need to rebuild our site. So let&amp;rsquo;s see how we can automate
that. Else the post won&amp;rsquo;t be visible until I next make a commit to my main branch.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: You Can Schedule Posts on Your Hugo Blog using Netlify and Gitlab CI</strong></p>
<p>This post in fact will be a scheduled post I&rsquo;m writing it on the 20th of November but it will be published on the 23rd of November.
Assuming we are using Netlify to deploy our website, what we will need to do is trigger a rebuild of our site every day using Gitlab CI.
Since Hugo builds a static site to publish our content after a set date we will need to rebuild our site. So let&rsquo;s see how we can automate
that. Else the post won&rsquo;t be visible until I next make a commit to my main branch.</p>
<p>I use the <code>date</code> field to determine when to make this post visible on my blog, set in the front matter of the post (markdown).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl">---
</span></span><span class="line"><span class="cl">title: &#34;TIL: You Can Schedule Posts on Your Hugo Blog using Netlify and Gitlab CI&#34;
</span></span><span class="line"><span class="cl">canonicalURL: https://haseebmajid.dev/posts/2022-11-23-til-you-can-schedule-posts-on-your-hugo-blog-using-netlify-and-gitlab-ci/
</span></span><span class="line"><span class="cl">date: 2022-11-23
</span></span><span class="line"><span class="cl">---
</span></span></code></pre></div><p>We can do this by first creating a build hook on Netlify, go to your <code>Site Settings</code> (on Netlify) then <code>Build and Deploy</code>. Copy the build hook
cURL request example which will look like <code>curl -X POST -d {} https://api.netlify.com/build_hooks/[id]</code>.</p>
<p><img
        loading="lazy"
        src="/posts/2022-11-23-til-you-can-schedule-posts-on-your-hugo-blog-using-netlify-and-gitlab-ci/images/netlify_build_hook.png"
        type=""
        alt="Netlify Build Hook"
        
      /></p>
<p>Next, go to Gitlab and open your <code>Settings &gt; CI/CD</code> settings and add a new variable called <code>NETLIFY_DEPLOY_WEBHOOK</code> and copy your build hook URL
<code>https://api.netlify.com/build_hooks/[id]</code>. Mark the variable as protected.</p>
<p><img
        loading="lazy"
        src="/posts/2022-11-23-til-you-can-schedule-posts-on-your-hugo-blog-using-netlify-and-gitlab-ci/images/gitlab_ci_variables.png"
        type=""
        alt="Gitlab CI Variables"
        
      /></p>
<p>Then update our <code>.gitlab-ci.yml</code> with the following:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">deploy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">deploy_site</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">deploy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">curl</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">GIT_STRATEGY</span><span class="p">:</span><span class="w"> </span><span class="l">none</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">curl -X POST -d &#39;{}&#39; $NETLIFY_BUILD_WEBHOOK</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">schedules</span><span class="w">
</span></span></span></code></pre></div><p>This job will run on a schedule, i.e. once a day, that will trigger the build hook and cause Netlify to rebuild our site.
This means any posts due to be published that day will be visible on our site.</p>
<p>Finally, let&rsquo;s schedule our CI job, go to <code>CI/CD &gt; Schedules</code>, and then create a new schedule. Set the <a href="https://crontab.guru">cron</a> to whatever
you want. My pipeline schedule is set as so:</p>
<p><img
        loading="lazy"
        src="/posts/2022-11-23-til-you-can-schedule-posts-on-your-hugo-blog-using-netlify-and-gitlab-ci/images/gitlab_ci_schedule.png"
        type=""
        alt="Gitlab CI Schedule"
        
      /></p>
<p>This will cause our website to build once a day, triggering a build on Netlify. So when the Gitlab CI job runs at midnight on the 23rd of November
this post will be visible to you all.</p>
<p>You can test your website using the <code>--buildFuture</code> flag i.e. <code>hugo server -D --buildFuture</code> when testing your site locally.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://hamy.xyz/labs/hugo-schedule-rebuilds-on-netlify">Inspired by</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: You can add a custom domain to your Goatcounter site</title>
      <link>https://haseebmajid.dev/posts/2022-11-22-til-you-can-add-a-custom-domain-to-your-goatcounter-site/</link>
      <pubDate>Tue, 22 Nov 2022 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2022-11-22-til-you-can-add-a-custom-domain-to-your-goatcounter-site/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: You can add a custom domain to your Goatcounter site&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When you create an account on Goatcounter and add a new site, you can view the analytics by going to &lt;code&gt;[sitecode].goatcounter.com&lt;/code&gt;.
For example, &lt;a href=&#34;https://haseebmajid.goatcounter.com&#34;&gt;&lt;code&gt;haseebmajid.goatcounter.com&lt;/code&gt;&lt;/a&gt;. However, we can use a custom domain, to view your analytics.&lt;/p&gt;
&lt;details
  class=&#34;notice warning&#34;
  open=&#34;true&#34;
&gt;
    &lt;summary class=&#34;notice-title&#34;&gt;Custom Domain&lt;/summary&gt;
  
  You will need a domain, that you can control the DNS of to do the following!
&lt;/details&gt;

&lt;h2 id=&#34;goatcounter&#34;&gt;Goatcounter&lt;/h2&gt;
&lt;p&gt;We can do this by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First go to your Goatcounter site
&lt;ul&gt;
&lt;li&gt;i.e. &lt;code&gt;haseebmajid.goatcounter.com&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Then go to &lt;code&gt;Settings&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Then &lt;code&gt;Domain settings&lt;/code&gt; &amp;gt; &lt;code&gt;Custom domain&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Add your domain
&lt;ul&gt;
&lt;li&gt;i.e. &lt;code&gt;stats.haseebmajid.dev&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Press the &lt;code&gt;Save&lt;/code&gt; button on the bottom left&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&#34;position:relative;padding-bottom:calc(100% / 1.78)&#34;&gt;
  &lt;iframe
    src=&#34;https://gfycat.com/ifr/InsignificantMemorableGreathornedowl&#34;
    frameborder=&#34;0&#34;
    scrolling=&#34;no&#34;
    width=&#34;100%&#34;
    height=&#34;100%&#34;
    style=&#34;position:absolute;top:0;left:0;&#34;
    allowfullscreen
  &gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h2 id=&#34;domain&#34;&gt;Domain&lt;/h2&gt;
&lt;p&gt;Finally, we need to go to our domain management tool, such as &lt;code&gt;namecheap&lt;/code&gt; and add a new entry:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: You can add a custom domain to your Goatcounter site</strong></p>
<p>When you create an account on Goatcounter and add a new site, you can view the analytics by going to <code>[sitecode].goatcounter.com</code>.
For example, <a href="https://haseebmajid.goatcounter.com"><code>haseebmajid.goatcounter.com</code></a>. However, we can use a custom domain, to view your analytics.</p>
<details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">Custom Domain</summary>
  
  You will need a domain, that you can control the DNS of to do the following!
</details>

<h2 id="goatcounter">Goatcounter</h2>
<p>We can do this by:</p>
<ul>
<li>First go to your Goatcounter site
<ul>
<li>i.e. <code>haseebmajid.goatcounter.com</code></li>
</ul>
</li>
<li>Then go to <code>Settings</code></li>
<li>Then <code>Domain settings</code> &gt; <code>Custom domain</code>
<ul>
<li>Add your domain
<ul>
<li>i.e. <code>stats.haseebmajid.dev</code></li>
</ul>
</li>
</ul>
</li>
<li>Press the <code>Save</code> button on the bottom left</li>
</ul>
<div style="position:relative;padding-bottom:calc(100% / 1.78)">
  <iframe
    src="https://gfycat.com/ifr/InsignificantMemorableGreathornedowl"
    frameborder="0"
    scrolling="no"
    width="100%"
    height="100%"
    style="position:absolute;top:0;left:0;"
    allowfullscreen
  ></iframe>
</div>

<h2 id="domain">Domain</h2>
<p>Finally, we need to go to our domain management tool, such as <code>namecheap</code> and add a new entry:</p>
<ul>
<li><code>CNAME stats.haseebmajid.dev haseebmajid.goatcounter.com</code></li>
</ul>
<details
  class="notice tip"
  open="true"
>
    <summary class="notice-title">Certificate</summary>
  
  It can up to an hour for the certificate to be created so try to visit your page in an hour.
</details>

]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How You Can Zoom in on Photos in your Hugo Posts</title>
      <link>https://haseebmajid.dev/posts/2022-11-21-til-how-you-can-zoom-in-on-photos-in-your-hugo-posts/</link>
      <pubDate>Mon, 21 Nov 2022 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2022-11-21-til-how-you-can-zoom-in-on-photos-in-your-hugo-posts/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How You Can Zoom in on Photos in your Hugo Posts&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You can see what we want to achieve in the GIF below:&lt;/p&gt;
&lt;div style=&#34;position:relative;padding-bottom:calc(100% / 1.78)&#34;&gt;
  &lt;iframe
    src=&#34;https://gfycat.com/ifr/MisguidedFondDingo&#34;
    frameborder=&#34;0&#34;
    scrolling=&#34;no&#34;
    width=&#34;100%&#34;
    height=&#34;100%&#34;
    style=&#34;position:absolute;top:0;left:0;&#34;
    allowfullscreen
  &gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h2 id=&#34;how&#34;&gt;How?&lt;/h2&gt;
&lt;p&gt;Add the following code to your Hugo blog in my case using the Papermod theme I add it to the &lt;code&gt;layouts/partials/extend_footer.html&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;script&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;na&#34;&gt;src&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;https://cdnjs.cloudflare.com/ajax/libs/medium-zoom/1.0.6/medium-zoom.min.js&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;na&#34;&gt;integrity&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;sha512-N9IJRoc3LaP3NDoiGkcPa4gG94kapGpaA5Zq9/Dr04uf5TbLFU5q0o8AbRhLKUUlp8QFS2u7S+Yti0U7QtuZvQ==&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;na&#34;&gt;crossorigin&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;anonymous&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;na&#34;&gt;referrerpolicy&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;no-referrer&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;script&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;script&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line hl&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;images&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;Array&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;from&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;document&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;querySelectorAll&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;.post-content img&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;images&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;forEach&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;((&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;img&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;mediumZoom&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;img&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;margin&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;cm&#34;&gt;/* The space outside the zoomed image */&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;scrollOffset&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;40&lt;/span&gt; &lt;span class=&#34;cm&#34;&gt;/* The number of pixels to scroll to close the zoom */&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;container&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;null&lt;/span&gt; &lt;span class=&#34;cm&#34;&gt;/* The viewport to render the zoom in */&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;template&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;null&lt;/span&gt; &lt;span class=&#34;cm&#34;&gt;/* The template element to display on zoom */&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;background&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;rgba(0, 0, 0, 0.8)&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;script&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The highlighted line may need to change depending on how your blog is setup. For my blog using the
&lt;a href=&#34;https://github.com/adityatelange/hugo-PaperMod&#34;&gt;PaperMod&lt;/a&gt; theme. This gets all the images on the page
&lt;code&gt;.post-content img&lt;/code&gt;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How You Can Zoom in on Photos in your Hugo Posts</strong></p>
<p>You can see what we want to achieve in the GIF below:</p>
<div style="position:relative;padding-bottom:calc(100% / 1.78)">
  <iframe
    src="https://gfycat.com/ifr/MisguidedFondDingo"
    frameborder="0"
    scrolling="no"
    width="100%"
    height="100%"
    style="position:absolute;top:0;left:0;"
    allowfullscreen
  ></iframe>
</div>

<h2 id="how">How?</h2>
<p>Add the following code to your Hugo blog in my case using the Papermod theme I add it to the <code>layouts/partials/extend_footer.html</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span>
</span></span><span class="line"><span class="cl">  <span class="na">src</span><span class="o">=</span><span class="s">&#34;https://cdnjs.cloudflare.com/ajax/libs/medium-zoom/1.0.6/medium-zoom.min.js&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="na">integrity</span><span class="o">=</span><span class="s">&#34;sha512-N9IJRoc3LaP3NDoiGkcPa4gG94kapGpaA5Zq9/Dr04uf5TbLFU5q0o8AbRhLKUUlp8QFS2u7S+Yti0U7QtuZvQ==&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="na">crossorigin</span><span class="o">=</span><span class="s">&#34;anonymous&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="na">referrerpolicy</span><span class="o">=</span><span class="s">&#34;no-referrer&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line hl"><span class="cl">  <span class="kr">const</span> <span class="nx">images</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">.</span><span class="nx">from</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="s2">&#34;.post-content img&#34;</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">  <span class="nx">images</span><span class="p">.</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">img</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">mediumZoom</span><span class="p">(</span><span class="nx">img</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">margin</span><span class="o">:</span> <span class="mi">0</span> <span class="cm">/* The space outside the zoomed image */</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">scrollOffset</span><span class="o">:</span> <span class="mi">40</span> <span class="cm">/* The number of pixels to scroll to close the zoom */</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">container</span><span class="o">:</span> <span class="kc">null</span> <span class="cm">/* The viewport to render the zoom in */</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">template</span><span class="o">:</span> <span class="kc">null</span> <span class="cm">/* The template element to display on zoom */</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">background</span><span class="o">:</span> <span class="s2">&#34;rgba(0, 0, 0, 0.8)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>The highlighted line may need to change depending on how your blog is setup. For my blog using the
<a href="https://github.com/adityatelange/hugo-PaperMod">PaperMod</a> theme. This gets all the images on the page
<code>.post-content img</code>.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://github.com/adityatelange/hugo-PaperMod/issues/384#issuecomment-899219940">Github Issue w/ Solution</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: How you can add goatcounter to your Hugo blog</title>
      <link>https://haseebmajid.dev/posts/2022-11-20-til-how-you-can-add-goatcounter-to-your-hugo-blog/</link>
      <pubDate>Sun, 20 Nov 2022 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2022-11-20-til-how-you-can-add-goatcounter-to-your-hugo-blog/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How you can add goatcounter to your Hugo blog&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In this TIL post, we will go over how you can add Goatcounter to your Hugo Blog.
Goatcounter is an open-source privacy-friendly analytics tool, an alternative
to Google Analytics.&lt;/p&gt;
&lt;details
  class=&#34;notice warning&#34;
  open=&#34;true&#34;
&gt;
    &lt;summary class=&#34;notice-title&#34;&gt;Goatcounter Account&lt;/summary&gt;
  
  This post assumes you have already created a Goatcounter account,
&lt;a href=&#34;https://www.goatcounter.com/&#34;&gt;more information here&lt;/a&gt;.
&lt;/details&gt;

&lt;p&gt;Luckily it is very easy to add Goatcounter to our blog. First, create a new &lt;code&gt;partial&lt;/code&gt; HTML file. In my example, it will be at &lt;code&gt;layouts/partial/analytics.html&lt;/code&gt;. However, you can call and place your file where ever you want, just remember it for later.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How you can add goatcounter to your Hugo blog</strong></p>
<p>In this TIL post, we will go over how you can add Goatcounter to your Hugo Blog.
Goatcounter is an open-source privacy-friendly analytics tool, an alternative
to Google Analytics.</p>
<details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">Goatcounter Account</summary>
  
  This post assumes you have already created a Goatcounter account,
<a href="https://www.goatcounter.com/">more information here</a>.
</details>

<p>Luckily it is very easy to add Goatcounter to our blog. First, create a new <code>partial</code> HTML file. In my example, it will be at <code>layouts/partial/analytics.html</code>. However, you can call and place your file where ever you want, just remember it for later.</p>
<p>The contents of the file should look like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;partials/analytics.html&#34;</span> 
</span></span><span class="line"><span class="cl">  <span class="na">data-goatcounter</span><span class="o">=</span><span class="s">&#34;https://{{ .Site.Params.goatcounter }}.goatcounter.com/count&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="na">async</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;//gc.zgo.at/count.js&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span></code></pre></div><blockquote>
<p>We will see how to pass the <code>goatcounter</code> param, later on, the <code>{{ .Site.Params.goatcounter }}</code></p>
</blockquote>
<p>Next, go look for a file which is used as the template for all of our pages.
In my case, it is located at <code>layouts/_default/baseof.html</code> and add the
following just above your footer:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go-html-template" data-lang="go-html-template"><span class="line"><span class="cl">  <span class="cp">{{-</span><span class="w"> </span><span class="nx">partialCached</span><span class="w"> </span><span class="s">&#34;header.html&#34;</span><span class="w"> </span><span class="na">.</span><span class="w"> </span><span class="na">.Page</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">main</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;main </span><span class="cp">{{-</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="o">(</span><span class="k">eq</span><span class="w"> </span><span class="na">.Kind</span><span class="w"> </span><span class="s">`page`</span><span class="o">)</span><span class="w"> </span><span class="cp">-}}{{-</span><span class="w"> </span><span class="k">print</span><span class="w"> </span><span class="s">&#34; post&#34;</span><span class="w"> </span><span class="cp">-}}{{-</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="cp">-}}</span><span class="s">&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="cp">{{-</span><span class="w"> </span><span class="k">block</span><span class="w"> </span><span class="s">&#34;main&#34;</span><span class="w"> </span><span class="na">.</span><span class="w"> </span><span class="cp">}}{{</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">main</span><span class="p">&gt;</span>
</span></span><span class="line hl"><span class="cl">  <span class="cp">{{-</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="na">.Site.Params.goatcounter</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line hl"><span class="cl">      <span class="cp">{{</span><span class="w"> </span><span class="nx">partial</span><span class="w"> </span><span class="s">&#34;analytics.html&#34;</span><span class="w"> </span><span class="na">.</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line hl"><span class="cl">  <span class="cp">{{-</span><span class="w"> </span><span class="k">end</span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl">  <span class="cp">{{</span><span class="w"> </span><span class="nx">partial</span><span class="w"> </span><span class="s">&#34;footer.html&#34;</span><span class="w"> </span><span class="na">.</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl">  <span class="cp">{{-</span><span class="w"> </span><span class="nx">partial</span><span class="w"> </span><span class="s">&#34;search.html&#34;</span><span class="w"> </span><span class="na">.</span><span class="w"> </span><span class="cp">-}}</span>
</span></span><span class="line"><span class="cl">  <span class="cp">{{-</span><span class="w"> </span><span class="k">block</span><span class="w"> </span><span class="s">&#34;body_end&#34;</span><span class="w"> </span><span class="na">.</span><span class="w"> </span><span class="cp">}}</span>
</span></span></code></pre></div><details
  class="notice tip"
  open="true"
>
    <summary class="notice-title">File Names</summary>
  
  Note here the file location and name here <code>{{ partial &quot;analytics.html&quot; . -}}</code>, matches what I said above.
</details>

<p>Finally, open your <code>config.yml</code> or <code>config.toml</code> and add the following. This is your site code on Goatcounter.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">params</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># ...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">goatcounter</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;haseebmajid&#34;</span><span class="w">
</span></span></span></code></pre></div><p>You can find your site code in your <code>settings &gt; sites</code>.</p>
<p><img
        loading="lazy"
        src="/posts/2022-11-20-til-how-you-can-add-goatcounter-to-your-hugo-blog/images/goatcounter.png"
        type=""
        alt="Goatcounter"
        
      /></p>
<p>That&rsquo;s it we&rsquo;ve added Goatcounter to our Hugo blog.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://stats.haseebmajid.dev/">Example Goatcounter</a></li>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/ea855a0c897e6238a6642507abb6ad92bd66e2c9">Blog using Goatcounter with Hugo</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: You can use Use `yq` to Mass Edit Markdown Files</title>
      <link>https://haseebmajid.dev/posts/2022-11-17-til-you-can-use-yq-to-mass-edit-markdown-files/</link>
      <pubDate>Thu, 17 Nov 2022 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2022-11-17-til-you-can-use-yq-to-mass-edit-markdown-files/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL you can use &lt;code&gt;yq&lt;/code&gt; to mass edit markdown files&lt;/strong&gt;&lt;/p&gt;
&lt;details
  class=&#34;notice tip&#34;
  open=&#34;false&#34;
&gt;
    &lt;summary class=&#34;notice-title&#34;&gt;yq&lt;/summary&gt;
  
  &lt;code&gt;yq&lt;/code&gt; is a tool similar to &lt;code&gt;jq&lt;/code&gt; except it allows you to edit, JSON, XML and YAML.
It has a very similar syntax to parse and edit files as &lt;code&gt;jq&lt;/code&gt; does.
&lt;/details&gt;

&lt;p&gt;I was recently adding new open graph images to all of my blog posts. After creating these images and storing them
next to the post, where the structure looks like:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL you can use <code>yq</code> to mass edit markdown files</strong></p>
<details
  class="notice tip"
  open="false"
>
    <summary class="notice-title">yq</summary>
  
  <code>yq</code> is a tool similar to <code>jq</code> except it allows you to edit, JSON, XML and YAML.
It has a very similar syntax to parse and edit files as <code>jq</code> does.
</details>

<p>I was recently adding new open graph images to all of my blog posts. After creating these images and storing them
next to the post, where the structure looks like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">content/posts/2020-01-13-using-tox-with-a-makefile-to-automate-python-related-tasks/
</span></span><span class="line"><span class="cl">├── images
</span></span><span class="line"><span class="cl">│   └── cover.png
</span></span><span class="line"><span class="cl">└── index.md
</span></span></code></pre></div><p>Now I needed to add the following to my frontmatter for all of my posts so from this</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">---
</span></span><span class="line"><span class="cl">title: &#34;TIL: You can use Use <span class="sb">`yq`</span> to Mass Edit Markdown Files&#34;
</span></span><span class="line"><span class="cl">---
</span></span></code></pre></div><p>to this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">---
</span></span><span class="line"><span class="cl">title: &#34;TIL: You can use Use <span class="sb">`yq`</span> to Mass Edit Markdown Files&#34;
</span></span><span class="line"><span class="cl">cover:
</span></span><span class="line"><span class="cl">  image: images/cover.png
</span></span><span class="line"><span class="cl">---
</span></span></code></pre></div><p>If I wanted to edit just that single post we could do something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">yq --front-matter<span class="o">=</span><span class="s2">&#34;process&#34;</span> -i <span class="s1">&#39;.cover.image = &#34;images/cover.png&#34;&#39;</span> content/posts/2020-01-13-using-tox-with-a-makefile-to-automate-python-related-tasks/index.md
</span></span></code></pre></div><p>Now I have ~50 odd posts I need to edit I don&rsquo;t want to run this command manually for each 😨.
Nobody wants to do that, so instead, we can run the following:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">find -name  <span class="s2">&#34;index.md&#34;</span> -exec yq --front-matter<span class="o">=</span><span class="s2">&#34;process&#34;</span> -i <span class="s1">&#39;.cover.image = &#34;images/cover.png&#34;&#39;</span> <span class="o">{}</span> <span class="se">\;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Or using fd a find alternative</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">fd <span class="s2">&#34;index.md&#34;</span> -x yq --front-matter<span class="o">=</span><span class="s2">&#34;process&#34;</span> -i <span class="s1">&#39;.cover.image = &#34;images/cover.png&#34;&#39;</span>
</span></span></code></pre></div><p>That&rsquo;s it, we just added a cover image to the frontmatter in all of our posts 😊.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://roneo.org/en/hugo-edit-yaml-files-from-the-cli-with-yq/">Inspiration for this post</a></li>
<li><a href="https://github.com/mikefarah/yq">yq</a></li>
<li><a href="https://github.com/sharkdp/fd">fd</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Why I moved from Gatsby to Hugo for this blog?</title>
      <link>https://haseebmajid.dev/posts/2022-11-17-why-i-moved-from-gatsby-to-hugo-for-this-blog/</link>
      <pubDate>Thu, 17 Nov 2022 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2022-11-17-why-i-moved-from-gatsby-to-hugo-for-this-blog/</guid>
      <description>&lt;h1 id=&#34;about-this-site&#34;&gt;About This Site&lt;/h1&gt;
&lt;p&gt;This site was built with &lt;a href=&#34;https://gohugo.io/&#34;&gt;hugo&lt;/a&gt; and the &lt;a href=&#34;https://github.com/hmajid2301/hugo-PaperModX&#34;&gt;PaperModX Theme&lt;/a&gt; (using a fork of a fork at the moment).&lt;/p&gt;
&lt;p&gt;I decided to go with an existing theme rather than creating my own this time, to one save time but also to give the
site a more consistent feel. I am no designer and I felt my last website (v3), really felt like a bunch of different
websites thrown together. It was a great way to learn React, TailwindCSS and a bunch of other technologies.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h1 id="about-this-site">About This Site</h1>
<p>This site was built with <a href="https://gohugo.io/">hugo</a> and the <a href="https://github.com/hmajid2301/hugo-PaperModX">PaperModX Theme</a> (using a fork of a fork at the moment).</p>
<p>I decided to go with an existing theme rather than creating my own this time, to one save time but also to give the
site a more consistent feel. I am no designer and I felt my last website (v3), really felt like a bunch of different
websites thrown together. It was a great way to learn React, TailwindCSS and a bunch of other technologies.</p>
<h2 id="technologies-used">Technologies Used</h2>
<ul>
<li><a href="https://gohugo.io/">Hugo</a>
<ul>
<li><a href="https://github.com/hmajid2301/hugo-PaperModX">PaperModX Theme</a>
<ul>
<li>My fork (of a fork, of a fork)</li>
</ul>
</li>
</ul>
</li>
<li><a href="https://www.goatcounter.com/">Goatcounter</a> for Analytics</li>
<li>Hosted by <a href="https://www.netlify.com/">Netlify</a></li>
<li>Using <a href="https://www.netlifycms.org">NetlifyCMS</a> for content management</li>
</ul>
<h2 id="why-move-to-hugo">Why Move to Hugo?</h2>
<p>I also had a bunch of issues even building the <a href="https://gitlab.com/hmajid2301/portfolio-site/-/pipelines">site recently</a>.
I had a scheduled job to rebuild it so that the stats page would update with the most viewed articles etc.
The final straw for me was not being to easily add a new page for talks. I recently was lucky enough to give a
talk at Europython and wanted to share that on my website but realised with my old Gatsby site that would be a bit of
a pain to add. I could also no longer easily upgrade my site to the latest version of Gatsby v4 due to the old
plugins I relied on.
Mostly it was essentially an issue with me, that I no longer wanted to put in the effort to maintain the site.</p>
<p>So I decided to take a look at something easier to maintain but that would still look great. I have been
learning Golang and decided to take a look at Hugo. One thing I noticed right away was how fast it was to
build the site. Roughly speaking the old site used to take ~120 seconds to build and this site takes &lt; 1 second.</p>
<p>Anecdotally I noticed the hot reloader seems to work better but again I was using a v2 of Gatsby.
Overall I am pretty happy with this new site. Using Hugo archetypes and page-bundles. I have moved
all of my articles within this repo, making it far easier to create new blog posts and test draft posts.</p>
<p>So hopefully I will be blogging more often 🤣 (I&rsquo;m looking at you 2 posts in 2022, as of the time of writing)!</p>
<h2 id="-older-iterations">👴 Older iterations:</h2>
<p>You can find older iterations of this site here:</p>
<ul>
<li><a href="https://v1.haseebmajid.dev">Version 1</a></li>
<li><a href="https://v2.haseebmajid.dev">Version 2</a></li>
<li><a href="https://v3.haseebmajid.dev">Version 3</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: You can Hash `None` in Python? What?</title>
      <link>https://haseebmajid.dev/posts/2022-11-15-til-you-can-hash-none-in-python-what/</link>
      <pubDate>Tue, 15 Nov 2022 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2022-11-15-til-you-can-hash-none-in-python-what/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL you can hash &lt;code&gt;None&lt;/code&gt; in Python&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Recently I saw some Python code which looked something like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;d&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kc&#34;&gt;None&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;value&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;s2&#34;&gt;&amp;#34;another_key&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;another_value&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It had never really occurred to me you could use &lt;code&gt;None&lt;/code&gt; (null) as a key to a dictionary before.
What this also meant is that &lt;code&gt;None&lt;/code&gt; must be hashable as only hashable objects can be keys in a dictionary.
These objects include &lt;code&gt;strings&lt;/code&gt;, &lt;code&gt;tuples&lt;/code&gt;, &lt;code&gt;sets&lt;/code&gt; but don&amp;rsquo;t include &lt;code&gt;lists&lt;/code&gt; as they are mutable and not
hashable.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL you can hash <code>None</code> in Python</strong></p>
<p>Recently I saw some Python code which looked something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">d</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kc">None</span><span class="p">:</span> <span class="s2">&#34;value&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;another_key&#34;</span><span class="p">:</span> <span class="s2">&#34;another_value&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>It had never really occurred to me you could use <code>None</code> (null) as a key to a dictionary before.
What this also meant is that <code>None</code> must be hashable as only hashable objects can be keys in a dictionary.
These objects include <code>strings</code>, <code>tuples</code>, <code>sets</code> but don&rsquo;t include <code>lists</code> as they are mutable and not
hashable.</p>
<details
  class="notice tip"
  open="false"
>
    <summary class="notice-title">Lists as keys?</summary>
  
  <p>Using a list as a key won&rsquo;t provide a consistent hash therefore you wouldn&rsquo;t be able
to find the value associated with that list. Again this is because lists are mutable
so their hash would change.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">In</span> <span class="p">[</span><span class="mi">1</span><span class="p">]:</span> <span class="n">a</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;a&#34;</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">In</span> <span class="p">[</span><span class="mi">2</span><span class="p">]:</span> <span class="nb">hash</span><span class="p">(</span><span class="n">a</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="o">---------------------------------------------------------------</span>
</span></span><span class="line"><span class="cl"><span class="ne">TypeError</span>                     <span class="n">Traceback</span> <span class="p">(</span><span class="n">most</span> <span class="n">recent</span> <span class="n">call</span> <span class="n">last</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">Cell</span> <span class="n">In</span> <span class="p">[</span><span class="mi">2</span><span class="p">],</span> <span class="n">line</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl"><span class="o">----&gt;</span> <span class="mi">1</span> <span class="nb">hash</span><span class="p">(</span><span class="n">a</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="ne">TypeError</span><span class="p">:</span> <span class="n">unhashable</span> <span class="nb">type</span><span class="p">:</span> <span class="s1">&#39;list&#39;</span>
</span></span></code></pre></div><p>In fact for this very reason you cannot even hash them using the builtin <code>hash</code> function in python.</p>

</details>

<h2 id="hash">Hash?</h2>
<p>If we do try to use the built-in <code>hash</code> function on <code>None</code> you will notice we do get a hash (int) as expected.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">In</span> <span class="p">[</span><span class="mi">1</span><span class="p">]:</span> <span class="nb">hash</span><span class="p">(</span><span class="kc">None</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">Out</span><span class="p">[</span><span class="mi">1</span><span class="p">]:</span> <span class="mi">8783956518372</span>
</span></span></code></pre></div><blockquote>
<p>So yeah you can use <code>None</code> as a key in a dictionary.</p>
</blockquote>
<details
  class="notice warning"
  open="false"
>
    <summary class="notice-title">JSON Caveat</summary>
  
  <p>One thing to note is when transforming a dictionary into JSON. The <code>None</code> will become a string.
So when you convert it back from JSON to a python dictionary it will be something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">d</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;None&#34;</span><span class="p">:</span> <span class="s2">&#34;value&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div>
</details>

]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: About Conditional Gitconfigs</title>
      <link>https://haseebmajid.dev/posts/2022-11-12-til-about-conditional-gitconfigs/</link>
      <pubDate>Sat, 12 Nov 2022 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2022-11-12-til-about-conditional-gitconfigs/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL you can have conditional sections in your &lt;code&gt;.gitconfig&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;As some of you may know, I keep my &lt;a href=&#34;https://gitlab.com/hmajid2301/dotfiles&#34;&gt;dotfiles&lt;/a&gt; in a git repo.
The problem with this approach is that my email in my &lt;code&gt;.gitconfig&lt;/code&gt; is set to &lt;code&gt;hello@haseebmajid.dev&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Where my config looks something like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-ini&#34; data-lang=&#34;ini&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;[user]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;na&#34;&gt;email&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;hello@haseebmajid.dev
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;  name = Haseeb Majid&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;However, when I am at work I need to use a different email to commit i.e. work email &lt;code&gt;haseeb@work.com&lt;/code&gt; not my personal
email. So I was wondering how could I do that; one way would be to change the file locally and just not commit it.
But of course, that is not ideal.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL you can have conditional sections in your <code>.gitconfig</code></strong></p>
<p>As some of you may know, I keep my <a href="https://gitlab.com/hmajid2301/dotfiles">dotfiles</a> in a git repo.
The problem with this approach is that my email in my <code>.gitconfig</code> is set to <code>hello@haseebmajid.dev</code>.</p>
<p>Where my config looks something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[user]</span>
</span></span><span class="line"><span class="cl">  <span class="na">email</span> <span class="o">=</span> <span class="s">hello@haseebmajid.dev
</span></span></span><span class="line"><span class="cl"><span class="s">  name = Haseeb Majid</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ...</span>
</span></span></code></pre></div><p>However, when I am at work I need to use a different email to commit i.e. work email <code>haseeb@work.com</code> not my personal
email. So I was wondering how could I do that; one way would be to change the file locally and just not commit it.
But of course, that is not ideal.</p>
<p>Then I discovered we can add other gitconfigs using the conditional <code>includeIf</code> clause. You can
read more about <a href="https://git-scm.com/docs/git-config#_conditional_includes">them here</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[user]</span>
</span></span><span class="line"><span class="cl">  <span class="na">email</span> <span class="o">=</span> <span class="s">hello@haseebmajid.dev
</span></span></span><span class="line"><span class="cl"><span class="s">  name = Haseeb Majid</span>
</span></span><span class="line"><span class="cl"><span class="k">[includeIf &#34;gitdir:/Users/&#34;]</span>
</span></span><span class="line"><span class="cl">  <span class="na">path</span> <span class="o">=</span> <span class="s">.gitconfig.mac</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ...</span>
</span></span></code></pre></div><p>I use a Mac at work and Linux at home so I can a basic check on folder structure i.e. <code>/home/</code> vs <code>/Users/</code>.
Then in the <code>.gitconfig.mac</code> we can do something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[user]</span>
</span></span><span class="line"><span class="cl">  <span class="na">email</span> <span class="o">=</span> <span class="s">haseeb@work.com</span>
</span></span></code></pre></div><p>This will replace the email with my work one when I commit when we run the <code>git</code> CLI command in a folder within <code>/Users/</code> folder.</p>
<details
  class="notice tip"
  open="true"
>
    <summary class="notice-title">Wild Card</summary>
  
  When we specify <code>/Users/</code> in the <code>gitdir</code> if clause it automatically treats it like <code>/Users/**</code>.
So any sub-directories within users also count.
</details>

<p>That&rsquo;s it! Now we can keep separate settings between work and home. We can also extend this to have different settings between
different OS&rsquo;s such as default editor.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>What is a TIL Post?</title>
      <link>https://haseebmajid.dev/posts/2022-11-11-what-is-a-til-post/</link>
      <pubDate>Fri, 11 Nov 2022 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2022-11-11-what-is-a-til-post/</guid>
      <description>&lt;h1 id=&#34;what-is-a-til-post&#34;&gt;What is a TIL post?&lt;/h1&gt;
&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2022-11-11-what-is-a-til-post/images/til.jpg&#34;
        type=&#34;&#34;
        alt=&#34;TIL&#34;
        
      /&gt;&lt;/p&gt;
&lt;p&gt;TIL - Stands for today I learnt. These posts will be shorts summaries of something I learnt &amp;ldquo;today&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;The TIL posts have a low barrier to entry and are a lot easier to write. Therefore they should encourage me
to post more often. In all my blog posts I try to post about things that I struggled to do. If I struggled
to implement a feature or build something. It shows that there wasn&amp;rsquo;t a tutorial or an easy example I could follow.
So then I usually decide to write my own, to make it easier for others. It also has a nice side effect of helping me remember the topic
better.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h1 id="what-is-a-til-post">What is a TIL post?</h1>
<p><img
        loading="lazy"
        src="/posts/2022-11-11-what-is-a-til-post/images/til.jpg"
        type=""
        alt="TIL"
        
      /></p>
<p>TIL - Stands for today I learnt. These posts will be shorts summaries of something I learnt &ldquo;today&rdquo;.</p>
<p>The TIL posts have a low barrier to entry and are a lot easier to write. Therefore they should encourage me
to post more often. In all my blog posts I try to post about things that I struggled to do. If I struggled
to implement a feature or build something. It shows that there wasn&rsquo;t a tutorial or an easy example I could follow.
So then I usually decide to write my own, to make it easier for others. It also has a nice side effect of helping me remember the topic
better.</p>
<p>Also, a TIL is a lot easier to write as they are a lot shorter. I cannot stress how important it is that they are shorter.
More seriously it also will hopefully force me to be more concise and try to write fewer words to convey what I mean.
These posts are also great for me, and I can reference them later if I forget something, which I definitely will.</p>
<details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">Disclaimer</summary>
  
  However, most likely it will be something I learnt recently, as I often don&rsquo;t have the energy/time to post
the same day I learn something new 😅.
</details>

<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://betatim.github.io/posts/til-explained/">My Inspiration</a></li>
<li><a href="https://rabea.dev/til/gitconfig-with-conditionals">More Inspiration</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Using Dotbot with plugins</title>
      <link>https://haseebmajid.dev/posts/2022-11-08-using-dotbot-with-plugins/</link>
      <pubDate>Tue, 08 Nov 2022 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2022-11-08-using-dotbot-with-plugins/</guid>
      <description>&lt;details
  class=&#34;notice warning&#34;
  open=&#34;true&#34;
&gt;
    &lt;summary class=&#34;notice-title&#34;&gt;Previous article&lt;/summary&gt;
  
  This article assumes you are familiar with dotfiles and Dobot.
If you want to know more about Dotbot &lt;a href=&#34;https://haseebmajid.dev/posts/2022-10-15-how-to-manage-your-dotfiles-with-dotbot&#34;&gt;click here&lt;/a&gt;
&lt;/details&gt;

&lt;p&gt;In this article I will show you how you can use Dotbot plugins. We can use Dotbot plugins to run new directives such as &lt;code&gt;apt&lt;/code&gt;.
So we can use the apt package manager, so install packages.&lt;/p&gt;
&lt;details
  class=&#34;notice note&#34;
  open=&#34;true&#34;
&gt;
    &lt;summary class=&#34;notice-title&#34;&gt;Tip&lt;/summary&gt;
  
  One useful use case is when we setup on a new system we may want to make we have some packages installed like &lt;code&gt;vim&lt;/code&gt; or &lt;code&gt;make&lt;/code&gt;.
We can automate some of this with Dotbot and its plugins.
&lt;/details&gt;

&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2022-11-08-using-dotbot-with-plugins/images/automate.png&#34;
        type=&#34;&#34;
        alt=&#34;Automate Meme&#34;
        
      /&gt;&lt;/p&gt;</description>
      <content:encoded><![CDATA[<details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">Previous article</summary>
  
  This article assumes you are familiar with dotfiles and Dobot.
If you want to know more about Dotbot <a href="/posts/2022-10-15-how-to-manage-your-dotfiles-with-dotbot">click here</a>
</details>

<p>In this article I will show you how you can use Dotbot plugins. We can use Dotbot plugins to run new directives such as <code>apt</code>.
So we can use the apt package manager, so install packages.</p>
<details
  class="notice note"
  open="true"
>
    <summary class="notice-title">Tip</summary>
  
  One useful use case is when we setup on a new system we may want to make we have some packages installed like <code>vim</code> or <code>make</code>.
We can automate some of this with Dotbot and its plugins.
</details>

<p><img
        loading="lazy"
        src="/posts/2022-11-08-using-dotbot-with-plugins/images/automate.png"
        type=""
        alt="Automate Meme"
        
      /></p>
<h2 id="current-setup">Current Setup</h2>
<p>Lets pretend we have something setup like this. We are using Dotbot with profiles.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">.
</span></span><span class="line"><span class="cl">├── ....
</span></span><span class="line"><span class="cl">├── bashrc
</span></span><span class="line"><span class="cl">├── .gitconfig
</span></span><span class="line"><span class="cl">├── install-profile
</span></span><span class="line"><span class="cl">├── install-standalone
</span></span><span class="line"><span class="cl">├── meta
</span></span><span class="line"><span class="cl">│   ├── configs
</span></span><span class="line"><span class="cl">│   │   └── git.yaml
</span></span><span class="line"><span class="cl">│   ├── dotbot
</span></span><span class="line"><span class="cl">│   ├── base.yaml
</span></span><span class="line"><span class="cl">│   └── profiles
</span></span><span class="line"><span class="cl">│       └── linux
</span></span><span class="line"><span class="cl">└── vscode
</span></span></code></pre></div><h2 id="optional-directives">(Optional) Directives</h2>
<p>Directives are commands that we specify in the <code>yaml</code> files that are parsed by Dotbot.
You can learn more about <a href="https://github.com/anishathalye/dotbot#link">directives here</a>.</p>
<h3 id="link">link</h3>
<p>This is the main directive we use to create symlinks between the repo and the system we run Dotbot on.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">link</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">~/.gitconfig</span><span class="p">:</span><span class="w"> </span><span class="l">.gitconfig</span><span class="w">
</span></span></span></code></pre></div><h3 id="create">create</h3>
<p>The create directive is used to create new (empty) folders. This code creates a new empty folder called <code>downloads</code> in your home directory.
If it doesn&rsquo;t already exist.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">create</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">~/downloads</span><span class="w">
</span></span></span></code></pre></div><h3 id="shell">shell</h3>
<p>Shell is used to run commands, well on the shell 😅. If we wanted to install starship prompt onto our system we could do something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">shell</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="l">curl -fsSL https://starship.rs/install.sh | sh -s -- --yes</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">stdout</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">stderr</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span></code></pre></div><p>Dotbot just runs these in the order it encounters them in our config files. If our <code>git.yaml</code> file looks something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">link</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">~/.gitconfig</span><span class="p">:</span><span class="w"> </span><span class="l">.gitconfig</span><span class="w">
</span></span></span></code></pre></div><p>It will only run the <code>link</code> directive, as this is the only file specified</p>
<h2 id="plugins">Plugins</h2>
<p>Now that we know what directives are, how do plugins fit into all this? Plugins allow us to extend Dotbot behaviour by adding new directives. For example an <code>apt</code>
directive so we can install packages using the apt package manager.</p>
<p>You can find a full list of <a href="https://github.com/anishathalye/dotbot/wiki/Plugins">Dotbot plugins here</a>.</p>
<h3 id="dobot-apt">dobot-apt</h3>
<details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">Profiles</summary>
  
  This assumes you are using Dotbot with profiles.
If not you can add the submodule to the root directory not to the <code>meta</code> folder.
</details>

<p>In this example we will add the <a href="https://github.com/bryant1410/dotbot-apt">dotbot-apt</a> plugin.</p>
<pre tabindex="0"><code>git submodule add https://github.com/bryant1410/dotbot-apt meta/dotbot-apt
</code></pre><p>Now we can create a new config in <code>meta/configs/packages.yaml</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">apt</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">zsh</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">jq</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">fzf</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">vim</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">make</span><span class="w">
</span></span></span></code></pre></div><p>This specifies a list of packages we want to install with <code>apt</code> including <code>jq</code>, <code>fzf</code> and <code>vim</code>.
Then in our <code>meta/profiles/linux</code> looks like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git
</span></span><span class="line"><span class="cl">packages-sudo
</span></span></code></pre></div><p>You may be wondering where the <code>-sudo</code> came from when our file is called <code>package</code>. The sudo suffix is used when we
want to run those directive with sudo. On most machines you can only install packages with sudo privilages.</p>
<p>Finally we need to update our <code>install-standalone</code> and <code>install-profile</code> scripts. Edit
the <code>cmd</code> variable like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">cmd</span><span class="o">=(</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">BASE_DIR</span><span class="si">}</span><span class="s2">/</span><span class="si">${</span><span class="nv">META_DIR</span><span class="si">}</span><span class="s2">/</span><span class="si">${</span><span class="nv">DOTBOT_DIR</span><span class="si">}</span><span class="s2">/</span><span class="si">${</span><span class="nv">DOTBOT_BIN</span><span class="si">}</span><span class="s2">&#34;</span> -d <span class="s2">&#34;</span><span class="si">${</span><span class="nv">BASE_DIR</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -p <span class="s2">&#34;</span><span class="si">${</span><span class="nv">BASE_DIR</span><span class="si">}</span><span class="s2">/</span><span class="si">${</span><span class="nv">META_DIR</span><span class="si">}</span><span class="s2">/dotbot-apt/apt.py&#34;</span>  -c <span class="s2">&#34;</span><span class="nv">$configFile</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="o">)</span>
</span></span></code></pre></div><p>Then we can run this command <code>./install-profile linux</code> to run all the config files specified in the linux profile.
It will ask for your sudo password when it reaches the <code>packages.yaml</code> file.</p>
<h2 id="finally">Finally</h2>
<p>That&rsquo;s it! We have now setup Dotbot to use plugins. There are a ton of plugins, I ended up using <code>apt</code> and <code>yay</code>. I was
jumping between an Ubuntu based system and an Arch system.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/dotfiles/-/tree/6b83e990861654506e8ecc756af75cf431438a4a">My Dotfiles using profiles</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Manage your Dotfiles with DotBot across Devices</title>
      <link>https://haseebmajid.dev/posts/2022-11-01-how-to-manage-dotfiles-with-dotbot-across-devices/</link>
      <pubDate>Tue, 01 Nov 2022 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2022-11-01-how-to-manage-dotfiles-with-dotbot-across-devices/</guid>
      <description>&lt;details
  class=&#34;notice warning&#34;
  open=&#34;true&#34;
&gt;
    &lt;summary class=&#34;notice-title&#34;&gt;Previous article&lt;/summary&gt;
  
  This article assumes you are familiar with dotfiles and Dobot.
If you want to know more about Dotbot &lt;a href=&#34;https://haseebmajid.dev/posts/2022-10-15-how-to-manage-your-dotfiles-with-dotbot&#34;&gt;click here&lt;/a&gt;
&lt;/details&gt;

&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2022-11-01-how-to-manage-dotfiles-with-dotbot-across-devices/images/meme.png&#34;
        type=&#34;&#34;
        alt=&#34;Dotfiles backup meme&#34;
        
      /&gt;
If you are like me you have devices, such as personal desktop and a work laptop. These devices share some dotfiles but also have specific program and applications.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Personal Desktop: Linux&lt;/li&gt;
&lt;li&gt;Work Laptop: Windows (WSL)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On Linux I use alacritty and in Windows I use Windows terminal I could copy all my dotfiles over and not worry
about which programs exist on which systems.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">Previous article</summary>
  
  This article assumes you are familiar with dotfiles and Dobot.
If you want to know more about Dotbot <a href="/posts/2022-10-15-how-to-manage-your-dotfiles-with-dotbot">click here</a>
</details>

<p><img
        loading="lazy"
        src="/posts/2022-11-01-how-to-manage-dotfiles-with-dotbot-across-devices/images/meme.png"
        type=""
        alt="Dotfiles backup meme"
        
      />
If you are like me you have devices, such as personal desktop and a work laptop. These devices share some dotfiles but also have specific program and applications.</p>
<p>For example:</p>
<ul>
<li>Personal Desktop: Linux</li>
<li>Work Laptop: Windows (WSL)</li>
</ul>
<p>On Linux I use alacritty and in Windows I use Windows terminal I could copy all my dotfiles over and not worry
about which programs exist on which systems.</p>
<p>If we are using dotbot to manage our dotfiles, we can use dotbot profiles. Profiles allow us to pick which dotfiles we want to install.
We could have a <code>windows</code> profile or a <code>linux</code> profile. Then specify different types of dotfiles in each of those profile files.</p>
<h2 id="current-setup">Current Setup</h2>
<p>So imagine we have something like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">.
</span></span><span class="line"><span class="cl"><span class="p">|</span>-- bashrc
</span></span><span class="line"><span class="cl"><span class="p">|</span>-- dotbot
</span></span><span class="line"><span class="cl"><span class="p">|</span>-- install
</span></span><span class="line"><span class="cl"><span class="p">|</span>-- .gitconfig
</span></span><span class="line"><span class="cl"><span class="p">|</span>-- install.conf.yaml
</span></span><span class="line"><span class="cl">└── vscode
</span></span></code></pre></div><p>Let us take a look how we can take a &ldquo;normal&rdquo; Dotbot project into one that uses profiles.</p>
<h2 id="how-to-move-to-profiles">How to move to profiles?</h2>
<h3 id="configs">configs</h3>
<p>First we will split the <code>install.conf.yaml</code> file into smaller files, I like to create files on type.
So for example I may create a file called <code>git.yaml</code> like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">link</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">~/.gitconfig</span><span class="p">:</span><span class="w"> </span><span class="l">.gitconfig</span><span class="w">
</span></span></span></code></pre></div><p>Then we need to move this file to <code>meta/configs</code>; So now our project looks something like</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">.
</span></span><span class="line"><span class="cl">├── ....
</span></span><span class="line"><span class="cl">├── bashrc
</span></span><span class="line"><span class="cl">├── .gitconfig
</span></span><span class="line"><span class="cl">├── meta
</span></span><span class="line"><span class="cl">│   ├── base.yaml
</span></span><span class="line"><span class="cl">│   └── configs
</span></span><span class="line"><span class="cl">│       └── git.yaml
</span></span><span class="line"><span class="cl">└── vscode
</span></span></code></pre></div><p>We also added a <code>base.yaml</code> file for cleaning up like a default &ldquo;job&rdquo;.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">clean</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;~&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">create</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">~/.config</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">defaults</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">link</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">relink</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span></code></pre></div><p>The default command here applys to all commands that follow this. You can read the <a href="https://github.com/anishathalye/dotbot#directives">docs here</a>.
For a more thorough explanation of the directives.</p>
<h3 id="profiles">profiles</h3>
<p>Next we want to create our profiles for our different devices. Let&rsquo;s see we want to create a Linux one. We create a called <code>meta/profiles/linux</code>.
In the file we write all the yaml files we want dotbot to execute for this profile. So at the moment our files would look something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git
</span></span></code></pre></div><p>When we specify the linux profile, we will see how to do this later, it will run the all the directives in the <code>git.yaml</code> file.
In this case it will copy over the gitconfig file in the project, to the host machine.</p>
<p>Now our setup looks something this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">.
</span></span><span class="line"><span class="cl">├── ....
</span></span><span class="line"><span class="cl">├── bashrc
</span></span><span class="line"><span class="cl">├── .gitconfig
</span></span><span class="line"><span class="cl">├── meta
</span></span><span class="line"><span class="cl">│   ├── configs
</span></span><span class="line"><span class="cl">│   │   └── git.yaml
</span></span><span class="line"><span class="cl">│   ├── base.yaml
</span></span><span class="line"><span class="cl">│   └── profiles
</span></span><span class="line"><span class="cl">│       └── linux
</span></span><span class="line"><span class="cl">└── vscode
</span></span></code></pre></div><h3 id="dotbot">dotbot</h3>
<p>We need to move the dotbot submodule to the <code>meta</code> folder. You can move your submodule by running <code>git mv dotbot meta/dotbot</code>.</p>
<h3 id="profile-script">Profile Script</h3>
<p>Finally we need to add a script that we can run to run all the directives specified in the profile. We will pass the profile as an argument.</p>
<p>Create a new file at the root of your project called <code>install-profile</code>, that looks like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env bash
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="nb">set</span> -e
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">BASE_CONFIG</span><span class="o">=</span><span class="s2">&#34;base&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">CONFIG_SUFFIX</span><span class="o">=</span><span class="s2">&#34;.yaml&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">META_DIR</span><span class="o">=</span><span class="s2">&#34;meta&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">CONFIG_DIR</span><span class="o">=</span><span class="s2">&#34;configs&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">PROFILES_DIR</span><span class="o">=</span><span class="s2">&#34;profiles&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">DOTBOT_DIR</span><span class="o">=</span><span class="s2">&#34;dotbot&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">DOTBOT_BIN</span><span class="o">=</span><span class="s2">&#34;bin/dotbot&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">BASE_DIR</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span><span class="nb">cd</span> <span class="s2">&#34;</span><span class="k">$(</span>dirname <span class="s2">&#34;</span><span class="si">${</span><span class="nv">BASH_SOURCE</span><span class="p">[0]</span><span class="si">}</span><span class="s2">&#34;</span><span class="k">)</span><span class="s2">&#34;</span> <span class="o">&amp;&amp;</span> <span class="nb">pwd</span><span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">BASE_DIR</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">git -C <span class="s2">&#34;</span><span class="si">${</span><span class="nv">BASE_DIR</span><span class="si">}</span><span class="s2">&#34;</span> submodule sync --quiet --recursive
</span></span><span class="line"><span class="cl">git submodule update --init --recursive <span class="s2">&#34;</span><span class="si">${</span><span class="nv">BASE_DIR</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">while</span> <span class="nv">IFS</span><span class="o">=</span> <span class="nb">read</span> -r config<span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="nv">CONFIGS</span><span class="o">+=</span><span class="s2">&#34; </span><span class="si">${</span><span class="nv">config</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span> &lt; <span class="s2">&#34;</span><span class="si">${</span><span class="nv">META_DIR</span><span class="si">}</span><span class="s2">/</span><span class="si">${</span><span class="nv">PROFILES_DIR</span><span class="si">}</span><span class="s2">/</span><span class="nv">$1</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">shift</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">for</span> config in <span class="si">${</span><span class="nv">CONFIGS</span><span class="si">}</span> <span class="si">${</span><span class="p">@</span><span class="si">}</span><span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> -e <span class="s2">&#34;\nConfigure </span><span class="nv">$config</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># create temporary file</span>
</span></span><span class="line"><span class="cl">    <span class="nv">configFile</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span>mktemp<span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nv">suffix</span><span class="o">=</span><span class="s2">&#34;-sudo&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> -e <span class="s2">&#34;</span><span class="k">$(</span>&lt;<span class="s2">&#34;</span><span class="si">${</span><span class="nv">BASE_DIR</span><span class="si">}</span><span class="s2">/</span><span class="si">${</span><span class="nv">META_DIR</span><span class="si">}</span><span class="s2">/</span><span class="si">${</span><span class="nv">BASE_CONFIG</span><span class="si">}${</span><span class="nv">CONFIG_SUFFIX</span><span class="si">}</span><span class="s2">&#34;</span><span class="k">)</span><span class="s2">\n</span><span class="k">$(</span>&lt;<span class="s2">&#34;</span><span class="si">${</span><span class="nv">BASE_DIR</span><span class="si">}</span><span class="s2">/</span><span class="si">${</span><span class="nv">META_DIR</span><span class="si">}</span><span class="s2">/</span><span class="si">${</span><span class="nv">CONFIG_DIR</span><span class="si">}</span><span class="s2">/</span><span class="si">${</span><span class="nv">config</span><span class="p">%</span><span class="s2">&#34;</span><span class="nv">$suffix</span><span class="s2">&#34;</span><span class="si">}${</span><span class="nv">CONFIG_SUFFIX</span><span class="si">}</span><span class="s2">&#34;</span><span class="k">)</span><span class="s2">&#34;</span> &gt; <span class="s2">&#34;</span><span class="nv">$configFile</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nv">cmd</span><span class="o">=(</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">BASE_DIR</span><span class="si">}</span><span class="s2">/</span><span class="si">${</span><span class="nv">META_DIR</span><span class="si">}</span><span class="s2">/</span><span class="si">${</span><span class="nv">DOTBOT_DIR</span><span class="si">}</span><span class="s2">/</span><span class="si">${</span><span class="nv">DOTBOT_BIN</span><span class="si">}</span><span class="s2">&#34;</span> -d <span class="s2">&#34;</span><span class="si">${</span><span class="nv">BASE_DIR</span><span class="si">}</span><span class="s2">&#34;</span> -c <span class="s2">&#34;</span><span class="nv">$configFile</span><span class="s2">&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="o">[[</span> <span class="nv">$config</span> <span class="o">==</span> *<span class="s2">&#34;sudo&#34;</span>* <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">        <span class="nv">cmd</span><span class="o">=(</span>sudo <span class="s2">&#34;</span><span class="si">${</span><span class="nv">cmd</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;</span><span class="si">${</span><span class="nv">cmd</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    rm -f <span class="s2">&#34;</span><span class="nv">$configFile</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">BASE_DIR</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span></code></pre></div><p>and add <code>install-standalone</code> to the root of the project.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env bash
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="nb">set</span> -e
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">BASE_CONFIG</span><span class="o">=</span><span class="s2">&#34;base&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">CONFIG_SUFFIX</span><span class="o">=</span><span class="s2">&#34;.yaml&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">META_DIR</span><span class="o">=</span><span class="s2">&#34;meta&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">CONFIG_DIR</span><span class="o">=</span><span class="s2">&#34;configs&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">PROFILES_DIR</span><span class="o">=</span><span class="s2">&#34;profiles&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">DOTBOT_DIR</span><span class="o">=</span><span class="s2">&#34;dotbot&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">DOTBOT_BIN</span><span class="o">=</span><span class="s2">&#34;bin/dotbot&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">BASE_DIR</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span><span class="nb">cd</span> <span class="s2">&#34;</span><span class="k">$(</span>dirname <span class="s2">&#34;</span><span class="si">${</span><span class="nv">BASH_SOURCE</span><span class="p">[0]</span><span class="si">}</span><span class="s2">&#34;</span><span class="k">)</span><span class="s2">&#34;</span> <span class="o">&amp;&amp;</span> <span class="nb">pwd</span><span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">BASE_DIR</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">git submodule update --init --recursive --remote
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">for</span> config in <span class="si">${</span><span class="p">@</span><span class="si">}</span><span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># create temporary file</span>
</span></span><span class="line"><span class="cl">    <span class="nv">configFile</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span>mktemp<span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nv">suffix</span><span class="o">=</span><span class="s2">&#34;-sudo&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> -e <span class="s2">&#34;</span><span class="k">$(</span>&lt;<span class="s2">&#34;</span><span class="si">${</span><span class="nv">BASE_DIR</span><span class="si">}</span><span class="s2">/</span><span class="si">${</span><span class="nv">META_DIR</span><span class="si">}</span><span class="s2">/</span><span class="si">${</span><span class="nv">BASE_CONFIG</span><span class="si">}${</span><span class="nv">CONFIG_SUFFIX</span><span class="si">}</span><span class="s2">&#34;</span><span class="k">)</span><span class="s2">\n</span><span class="k">$(</span>&lt;<span class="s2">&#34;</span><span class="si">${</span><span class="nv">BASE_DIR</span><span class="si">}</span><span class="s2">/</span><span class="si">${</span><span class="nv">META_DIR</span><span class="si">}</span><span class="s2">/</span><span class="si">${</span><span class="nv">CONFIG_DIR</span><span class="si">}</span><span class="s2">/</span><span class="si">${</span><span class="nv">config</span><span class="p">%</span><span class="s2">&#34;</span><span class="nv">$suffix</span><span class="s2">&#34;</span><span class="si">}${</span><span class="nv">CONFIG_SUFFIX</span><span class="si">}</span><span class="s2">&#34;</span><span class="k">)</span><span class="s2">&#34;</span> &gt; <span class="s2">&#34;</span><span class="nv">$configFile</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nv">cmd</span><span class="o">=(</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">BASE_DIR</span><span class="si">}</span><span class="s2">/</span><span class="si">${</span><span class="nv">META_DIR</span><span class="si">}</span><span class="s2">/</span><span class="si">${</span><span class="nv">DOTBOT_DIR</span><span class="si">}</span><span class="s2">/</span><span class="si">${</span><span class="nv">DOTBOT_BIN</span><span class="si">}</span><span class="s2">&#34;</span> -d <span class="s2">&#34;</span><span class="si">${</span><span class="nv">BASE_DIR</span><span class="si">}</span><span class="s2">&#34;</span> -c <span class="s2">&#34;</span><span class="nv">$configFile</span><span class="s2">&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="o">[[</span> <span class="nv">$config</span> <span class="o">==</span> *<span class="s2">&#34;sudo&#34;</span>* <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">        <span class="nv">cmd</span><span class="o">=(</span>sudo <span class="s2">&#34;</span><span class="si">${</span><span class="nv">cmd</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;</span><span class="si">${</span><span class="nv">cmd</span><span class="p">[@]</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    rm -f <span class="s2">&#34;</span><span class="nv">$configFile</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">BASE_DIR</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span></code></pre></div><p>Finally we should have something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">.
</span></span><span class="line"><span class="cl">├── ....
</span></span><span class="line"><span class="cl">├── bashrc
</span></span><span class="line"><span class="cl">├── .gitconfig
</span></span><span class="line"><span class="cl">├── install-profile
</span></span><span class="line"><span class="cl">├── install-standalone
</span></span><span class="line"><span class="cl">├── meta
</span></span><span class="line"><span class="cl">│   ├── configs
</span></span><span class="line"><span class="cl">│   │   └── git.yaml
</span></span><span class="line"><span class="cl">│   ├── dotbot
</span></span><span class="line"><span class="cl">│   ├── base.yaml
</span></span><span class="line"><span class="cl">│   └── profiles
</span></span><span class="line"><span class="cl">│       └── linux
</span></span><span class="line"><span class="cl">└── vscode
</span></span></code></pre></div><h2 id="finally">Finally!</h2>
<p>Then we can install the linux specific dotfiles by running <code>./install-profile linux</code>. Now of course this is a very simplified example.
In reality you&rsquo;d have lots of profiles and different configs in those profiles. But I wanted to show a simple example here, to
make it easier to follow.</p>
<p>Speaking of finishing 😭 &hellip;.</p>
<div style="position:relative;padding-bottom:calc(100% / 1.78)">
  <iframe
    src="https://gfycat.com/ifr/ContentScrawnyGrackle"
    frameborder="0"
    scrolling="no"
    width="100%"
    height="100%"
    style="position:absolute;top:0;left:0;"
    allowfullscreen
  ></iframe>
</div>

<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/dotfiles/-/tree/6b83e990861654506e8ecc756af75cf431438a4a">My Dotfiles using profiles</a></li>
<li><a href="https://github.com/anishathalye/dotbot/wiki/Tips-and-Tricks#more-advanced-setup">Dotbot profiles tutorial</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>My current VSCode Setup (Extensions and Settings)</title>
      <link>https://haseebmajid.dev/posts/2022-10-16-my-current-vscode-setup-extensions-and-settings/</link>
      <pubDate>Sun, 16 Oct 2022 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2022-10-16-my-current-vscode-setup-extensions-and-settings/</guid>
      <description>&lt;p&gt;Today I will go over my current VSCode setup covering both extensions and settings.&lt;/p&gt;
&lt;details
  class=&#34;notice warning&#34;
  open=&#34;true&#34;
&gt;
    &lt;summary class=&#34;notice-title&#34;&gt;Out of date&lt;/summary&gt;
  
  This article may be out of date, by the time you are reading this. It is accurate as of
16th October 2022.
&lt;/details&gt;

&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2022-10-16-my-current-vscode-setup-extensions-and-settings/images/vscode-meme.png&#34;
        type=&#34;&#34;
        alt=&#34;vscode meme&#34;
        
      /&gt;&lt;/p&gt;
&lt;h2 id=&#34;extensions&#34;&gt;Extensions&lt;/h2&gt;
&lt;p&gt;Here we will go over some extensions I find very useful:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Full list of my &lt;a href=&#34;https://gitlab.com/hmajid2301/dotfiles/-/blob/6b83e990861654506e8ecc756af75cf431438a4a/vscode/extensions.txt&#34;&gt;plugins here&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;better-comments&#34;&gt;Better Comments&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Link: &lt;a href=&#34;https://marketplace.visualstudio.com/items?itemName=aaron-bond.better-comments&#34;&gt;Better Comments&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As the name implies it improves your comments. Makes them look nicer if you add certain annotations.
Such as adding &lt;code&gt;TODO&lt;/code&gt; to the comment will turn the comment orange. See the image below:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Today I will go over my current VSCode setup covering both extensions and settings.</p>
<details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">Out of date</summary>
  
  This article may be out of date, by the time you are reading this. It is accurate as of
16th October 2022.
</details>

<p><img
        loading="lazy"
        src="/posts/2022-10-16-my-current-vscode-setup-extensions-and-settings/images/vscode-meme.png"
        type=""
        alt="vscode meme"
        
      /></p>
<h2 id="extensions">Extensions</h2>
<p>Here we will go over some extensions I find very useful:</p>
<ul>
<li>Full list of my <a href="https://gitlab.com/hmajid2301/dotfiles/-/blob/6b83e990861654506e8ecc756af75cf431438a4a/vscode/extensions.txt">plugins here</a></li>
</ul>
<h3 id="better-comments">Better Comments</h3>
<ul>
<li>Link: <a href="https://marketplace.visualstudio.com/items?itemName=aaron-bond.better-comments">Better Comments</a></li>
</ul>
<p>As the name implies it improves your comments. Makes them look nicer if you add certain annotations.
Such as adding <code>TODO</code> to the comment will turn the comment orange. See the image below:</p>
<p><img
        loading="lazy"
        src="/posts/2022-10-16-my-current-vscode-setup-extensions-and-settings/images/better-comments.png"
        type=""
        alt="Better Comments"
        
      /></p>
<p>I like this extension because it makes certain comments stand out.
I use the <code>TODO</code> comments a lot 😅. You can configure it to change what annotations to use and which colours
they should be.</p>
<h3 id="error-lens">Error Lens</h3>
<ul>
<li>Link: <a href="https://marketplace.visualstudio.com/items?itemName=usernamehw.errorlens">Error Lens</a></li>
</ul>
<p>One of my favourite vscode extensions, what this extension does is it shows the errors inline.
A bit like Gitlens (further down), so that you don&rsquo;t have to hover over your code to see the error message.</p>
<p><img
        loading="lazy"
        src="/posts/2022-10-16-my-current-vscode-setup-extensions-and-settings/images/error-lens.png"
        type=""
        alt="Error Lens"
        
      /></p>
<p>I found out about this extension while at EuroPython earlier this year. I Have been using it ever since
and do love it. Would recommend it, the colour of the line changes depending on the type of error
such as warning vs error.</p>
<p>Give it a go and see what you think.</p>
<h3 id="dracula-official">Dracula Official</h3>
<ul>
<li>Link: <a href="https://marketplace.visualstudio.com/items?itemName=dracula-theme.theme-dracula">Dracula Official</a></li>
</ul>
<p>I like using the Dracula theme, so I tend either use this or Monokai Pro. At the moment
I&rsquo;m using this theme.</p>
<p>One other thing I like about the Dracula theme is that it supports a ton of other programs and
applications so you can style your entire system with Dracula for a more uniform look.
<a href="https://draculatheme.com/">Click here</a> for more info.</p>
<h3 id="fluent-icons">Fluent Icons</h3>
<ul>
<li>Link: <a href="https://marketplace.visualstudio.com/items?itemName=miguelsolorio.fluent-icons">Fluent Icons</a>.</li>
</ul>
<p>Some people may not be aware but you can change what they call the product icons in vscode.
When we say product icons what we mean are the icons within the application such as for the <code>File Explorer</code>,
or <code>Debugger</code> etc.</p>
<p>I like to use Fluent Icons for my icons within vscode, I like the simpler look of the icons and they seem
to be a bit clearer as well.</p>
<p>You can set icons very much like you can set the theme, using the command palette or within your settings.
For example in my <code>settings.json</code> file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="s2">&#34;workbench.productIconTheme&#34;</span><span class="err">:</span> <span class="s2">&#34;fluent-icons&#34;</span><span class="err">,</span>
</span></span></code></pre></div><p><img
        loading="lazy"
        src="/posts/2022-10-16-my-current-vscode-setup-extensions-and-settings/images/fluent-icons.png"
        type=""
        alt="Fluent Icons"
        
      /></p>
<h3 id="which-key">Which Key</h3>
<ul>
<li>Link: <a href="https://marketplace.visualstudio.com/items?itemName=VSpaceCode.whichkey">Which Key</a></li>
</ul>
<p>This adds a which-key like menu to vscode, I believe this is copied over from neovim.
It looks something like this:</p>
<p><img
        loading="lazy"
        src="/posts/2022-10-16-my-current-vscode-setup-extensions-and-settings/images/which-key.png"
        type=""
        alt="Which Key"
        
      /></p>
<p>In my case when I press the <code>/</code> key it opens up the menu, and the menu can be nested for better organisation.
But I use it to access common commands I run. Below you can see some of the commands behind sub-menus.</p>
<p><img
        loading="lazy"
        src="/posts/2022-10-16-my-current-vscode-setup-extensions-and-settings/images/which-key.gif"
        type=""
        alt="Which Key GIF"
        
      /></p>
<p>Overall I am very happy with this extension it does take a while to customise but it provides a ton of value.
I would work out which commands you use a lot and cannot/do not want to put behind a shortcut.
Put them in this which key dropdown.</p>
<p><a href="https://gitlab.com/hmajid2301/dotfiles/-/blob/6b83e990861654506e8ecc756af75cf431438a4a/vscode/linux/settings.json#L240-825">My which-key config</a> in my <code>settings.json</code> file.</p>
<h3 id="vim">vim</h3>
<ul>
<li>Link: <a href="https://marketplace.visualstudio.com/items?itemName=vscodevim.vim">Vim</a></li>
</ul>
<p>By far the most important extension in any text editor I use. This plugin adds vim key bindings to vscode.
Super important for me. I am not amazing at vim but I use a decent number of vim shortcuts in my workflow
and would miss not having them.</p>
<h3 id="vscode-pets">vscode-pets</h3>
<ul>
<li>Link: <a href="https://marketplace.visualstudio.com/items?itemName=tonybaloney.vscode-pets">vscode-pets</a></li>
</ul>
<p>Arguably the most important extensions and this is why vscode beats out any other editor. You can have
PETS in vscode! Your own pets you can play with.</p>
<p><img
        loading="lazy"
        src="/posts/2022-10-16-my-current-vscode-setup-extensions-and-settings/images/vscode-pets.gif"
        type=""
        alt="vscode pets"
        
      /></p>
<h2 id="settings">Settings</h2>
<p>In this section I will go over some of my interesting settings for vscode.</p>
<p><a href="https://gitlab.com/hmajid2301/dotfiles/-/blob/6b83e990861654506e8ecc756af75cf431438a4a/vscode/linux/settings.json">settings.json</a>.</p>
<h3 id="fonts">Fonts</h3>
<p>I swap between MonoLisa (paid font) and Fira Code (free font) but recently have been using MonoLisa as my monospace font of choice.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;editor.fontFamily&#34;</span><span class="p">:</span> <span class="s2">&#34;&#39;MonoLisa&#39;, &#39;Droid Sans Mono&#39;, &#39;monospace&#39;, &#39;Noto Color Emoji&#39;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// &#34;editor.fontFamily&#34;: &#34;&#39;Fira Code&#39;, &#39;Droid Sans Mono&#39;, &#39;monospace&#39;, &#39;Noto Color Emoji&#39;&#34;,
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="nt">&#34;debug.console.fontFamily&#34;</span><span class="p">:</span> <span class="s2">&#34;&#39;MonoLisa&#39;, &#39;Droid Sans Mono&#39;, &#39;monospace&#39;, &#39;Noto Color Emoji&#39;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="golang">Golang</h3>
<p>I have some specific settings for Golang:</p>
<p>Before</p>
<p><img
        loading="lazy"
        src="/posts/2022-10-16-my-current-vscode-setup-extensions-and-settings/images/golang-before.png"
        type=""
        alt="Golang vscode before"
        
      /></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;editor.tokenColorCustomizations&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;[Dracula]&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;textMateRules&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nt">&#34;scope&#34;</span><span class="p">:</span> <span class="s2">&#34;source.go&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nt">&#34;settings&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;foreground&#34;</span><span class="p">:</span> <span class="s2">&#34;#6dd8ce&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;go.inlayHints.parameterNames&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;go.inlayHints.compositeLiteralFields&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;gopls&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;ui.semanticTokens&#34;</span><span class="p">:</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;go.lintTool&#34;</span><span class="p">:</span> <span class="s2">&#34;golangci-lint&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;go.lintFlags&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;--fast&#34;</span><span class="p">,</span> <span class="s2">&#34;--enable&#34;</span><span class="p">,</span> <span class="s2">&#34;unconvert&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><ul>
<li><code>go.inlayHints</code>: Used to show parameter arguments in functions, you can see it in the image below like <code>a...</code> or <code>ctx</code></li>
<li><code>go.lint</code>: I Set golangci-lint as my default linter, it has a lot of &ldquo;sub-linters&rdquo; or tools we can use. We are only enabling <code>unconvert</code> at the moment to detect unnecessary type conversions</li>
<li><code>gopls</code>: Semantic tokens improve the syntax highlighting</li>
<li><code>editor.tokenColorCustomizations</code>: Allows us to create some specific rules for syntax highlighting</li>
</ul>
<p>After</p>
<p><img
        loading="lazy"
        src="/posts/2022-10-16-my-current-vscode-setup-extensions-and-settings/images/golang-after.png"
        type=""
        alt="Golang vscode after"
        
      /></p>
<h3 id="vim-1">Vim</h3>
<p>Most of the interesting settings are for vim;</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;vim.smartRelativeLine&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>In normal mode gives us relative lines to the current lines makes easier moving using vim keys such <code>h j k l</code></p>
<p><img
        loading="lazy"
        src="/posts/2022-10-16-my-current-vscode-setup-extensions-and-settings/images/smart-relative-lines.png"
        type=""
        alt="Smart Relative Lines"
        
      /></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;vim.insertModeKeyBindings&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;before&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;j&#34;</span><span class="p">,</span> <span class="s2">&#34;j&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;after&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;&lt;Esc&gt;&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This means we can access normal mode by pressing <code>j + j</code> instead of <code>&lt;ESC&gt;</code> key.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;vim.visualModeKeyBindings&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;before&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;&gt;&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;after&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;&gt;&#34;</span><span class="p">,</span> <span class="s2">&#34;g&#34;</span><span class="p">,</span> <span class="s2">&#34;v&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;before&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;&lt;&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;after&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;&lt;&#34;</span><span class="p">,</span> <span class="s2">&#34;g&#34;</span><span class="p">,</span> <span class="s2">&#34;v&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This allows us to ident and outdent without needing to keep reselecting the visual block.</p>
<p><img
        loading="lazy"
        src="/posts/2022-10-16-my-current-vscode-setup-extensions-and-settings/images/outdent-indent.gif"
        type=""
        alt="Outdent Indent vim"
        
      /></p>
<h4 id="easymotion">Easymotion</h4>
<p>Easy motion is a great plugin for vim which makes it much easier to move around the document.
I have <code>&lt;SPACE&gt; + a</code> shortcut setup with a slightly more complicated easy motion shortcut
which will allow us to move anywhere in the document.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;vim.easymotion&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;vim.leader&#34;</span><span class="p">:</span> <span class="s2">&#34;&lt;Space&gt;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;vim.easymotionMarkerBackgroundColor&#34;</span><span class="p">:</span> <span class="s2">&#34;#7e57c2&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;vim.normalModeKeyBindingsNonRecursive&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;before&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;&lt;Space&gt;&#34;</span><span class="p">,</span> <span class="s2">&#34;a&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;after&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;leader&#34;</span><span class="p">,</span> <span class="s2">&#34;leader&#34;</span><span class="p">,</span> <span class="s2">&#34;leader&#34;</span><span class="p">,</span> <span class="s2">&#34;b&#34;</span><span class="p">,</span> <span class="s2">&#34;d&#34;</span><span class="p">,</span> <span class="s2">&#34;w&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="p">],</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p><img
        loading="lazy"
        src="/posts/2022-10-16-my-current-vscode-setup-extensions-and-settings/images/easymotion.gif"
        type=""
        alt="easymotion"
        
      /></p>
<h4 id="normal-key-bindings">Normal Key Bindings</h4>
<p>For some other bindings, the <code>g</code> command which opens the which menu you saw above and <code>&lt;SPACE&gt; + w</code> will open a new vertical split.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;vim.normalModeKeyBindingsNonRecursive&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;before&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;g&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;commands&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;whichkey.show&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;before&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;&lt;leader&gt;&#34;</span><span class="p">,</span> <span class="s2">&#34;w&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;commands&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;workbench.action.splitEditor&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h2 id="keybindings">Keybindings</h2>
<p><a href="https://gitlab.com/hmajid2301/dotfiles/-/blob/6b83e990861654506e8ecc756af75cf431438a4a/vscode/linux/keybindings.json">keybindings.json</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">[</span>
</span></span><span class="line"><span class="cl"> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;ctrl+j&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;selectNextSuggestion&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;when&#34;</span><span class="p">:</span> <span class="s2">&#34;editorTextFocus &amp;&amp; suggestWidgetMultipleSuggestions &amp;&amp; suggestWidgetVisible&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;ctrl+k&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;selectPrevSuggestion&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;when&#34;</span><span class="p">:</span> <span class="s2">&#34;editorTextFocus &amp;&amp; suggestWidgetMultipleSuggestions &amp;&amp; suggestWidgetVisible&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span></code></pre></div><p>Allow us to use <code>ctrl+j</code> and <code>ctrl+k</code> to navigate auto-suggestions from vscode rather than using the arrow keys.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">[</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;ctrl+l&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;workbench.action.focusRightGroup&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;ctrl+h&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;workbench.action.focusLeftGroup&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;ctrl+h&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;workbench.action.navigateLeft&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;ctrl+l&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;workbench.action.navigateRight&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;ctrl+k&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;workbench.action.navigateUp&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;ctrl+j&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;workbench.action.navigateDown&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span></code></pre></div><p>Used to navigate across vscode using ctrl and the vim replacement arrow keys.
Allow us to again not have to use the mouse we can jump to any section of vscode, i.e. terminal to the editor.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">[</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;enter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;list.select&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;when&#34;</span><span class="p">:</span> <span class="s2">&#34;explorerViewletVisible &amp;&amp; filesExplorerFocus&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;l&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;list.select&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;when&#34;</span><span class="p">:</span> <span class="s2">&#34;explorerViewletVisible &amp;&amp; filesExplorerFocus &amp;&amp; !inputFocus&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;o&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;list.toggleExpand&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;when&#34;</span><span class="p">:</span> <span class="s2">&#34;explorerViewletVisible &amp;&amp; filesExplorerFocus &amp;&amp; !inputFocus&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;h&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;list.collapse&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;when&#34;</span><span class="p">:</span> <span class="s2">&#34;explorerViewletVisible &amp;&amp; filesExplorerFocus &amp;&amp; !inputFocus&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;a&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;explorer.newFile&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;when&#34;</span><span class="p">:</span> <span class="s2">&#34;filesExplorerFocus &amp;&amp; !inputFocus&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;shift+a&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;explorer.newFolder&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;when&#34;</span><span class="p">:</span> <span class="s2">&#34;filesExplorerFocus &amp;&amp; !inputFocus&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;r&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;renameFile&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;when&#34;</span><span class="p">:</span> <span class="s2">&#34;explorerViewletVisible &amp;&amp; filesExplorerFocus &amp;&amp; !explorerResourceIsRoot &amp;&amp; !explorerResourceReadonly &amp;&amp; !inputFocus&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;enter&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;-renameFile&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;when&#34;</span><span class="p">:</span> <span class="s2">&#34;explorerViewletVisible &amp;&amp; filesExplorerFocus &amp;&amp; !explorerResourceIsRoot &amp;&amp; !explorerResourceReadonly &amp;&amp; !inputFocus&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span></code></pre></div><p>These settings allow us to use the keyboard shortcut to do common actions with files. Such as creating files, creating folders etc. Again this helps us avoid using the mouse.</p>
<h2 id="closing-thoughts">Closing Thoughts</h2>
<p>This post has ballooned a lot more than I expected, so I&rsquo;ll stop here. Perhaps I&rsquo;ll do a more in-depth post into how I use vim
with vscode, and avoid using the mouse as much as possible. But I&rsquo;m getting a lot better at myself at the moment.</p>
<p>And finally, I will leave you with this:</p>
<div style="position:relative;padding-bottom:calc(100% / 1.78)">
  <iframe
    src="https://gfycat.com/ifr/PlaintiveAdorableBactrian"
    frameborder="0"
    scrolling="no"
    width="100%"
    height="100%"
    style="position:absolute;top:0;left:0;"
    allowfullscreen
  ></iframe>
</div>

<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/dotfiles/-/tree/main/vscode">My vscode settings</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Manage Your Dotfiles With Dotbot</title>
      <link>https://haseebmajid.dev/posts/2022-10-15-how-to-manage-your-dotfiles-with-dotbot/</link>
      <pubDate>Sat, 15 Oct 2022 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2022-10-15-how-to-manage-your-dotfiles-with-dotbot/</guid>
      <description>&lt;p&gt;If you&amp;rsquo;re like me you find yourself moving between multiple systems. Whether that be between my personal
desktop and my work laptop or distro hopping on Linux. See relevant meme below:&lt;/p&gt;
&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2022-10-15-how-to-manage-your-dotfiles-with-dotbot/images/distro_hopping.jpg&#34;
        type=&#34;&#34;
        alt=&#34;Distro Hopping Meme&#34;
        
      /&gt;&lt;/p&gt;
&lt;details
  class=&#34;notice info&#34;
  open=&#34;true&#34;
&gt;
    &lt;summary class=&#34;notice-title&#34;&gt;What are dotfiles?&lt;/summary&gt;
  
  &lt;p&gt;Many tools/program store their configuration files as files on your machine.
On Linux you will often find these in &lt;code&gt;~/.config&lt;/code&gt; directory.&lt;/p&gt;
&lt;p&gt;Some common examples of dotfiles:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- .vimrc
- .bashrc
- .gitconfig
&lt;/code&gt;&lt;/pre&gt;

&lt;/details&gt;

&lt;p&gt;I wanted to find an easy way to manage my dotfiles and share them between mutiple systems.
I also wanted a easy way to install software/tools I used between my systems.
Introducing &lt;a href=&#34;https://github.com/anishathalye/dotbot&#34;&gt;DotBot&lt;/a&gt;, a tool that provides an easy
way to manage our dotfiles using VCS (git).&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>If you&rsquo;re like me you find yourself moving between multiple systems. Whether that be between my personal
desktop and my work laptop or distro hopping on Linux. See relevant meme below:</p>
<p><img
        loading="lazy"
        src="/posts/2022-10-15-how-to-manage-your-dotfiles-with-dotbot/images/distro_hopping.jpg"
        type=""
        alt="Distro Hopping Meme"
        
      /></p>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">What are dotfiles?</summary>
  
  <p>Many tools/program store their configuration files as files on your machine.
On Linux you will often find these in <code>~/.config</code> directory.</p>
<p>Some common examples of dotfiles:</p>
<pre><code>- .vimrc
- .bashrc
- .gitconfig
</code></pre>

</details>

<p>I wanted to find an easy way to manage my dotfiles and share them between mutiple systems.
I also wanted a easy way to install software/tools I used between my systems.
Introducing <a href="https://github.com/anishathalye/dotbot">DotBot</a>, a tool that provides an easy
way to manage our dotfiles using VCS (git).</p>
<h2 id="how-does-it-work">How does it work?</h2>
<p>Dotbot works by creating symlinks between files in your git repo to a location on your filesystem i.e <code>~/dotfiles/bashrc -&gt; ~/.bashrc</code>.
So this means if we edit either file it will edit in both places. Typically I will edit the files in the dotfiles repo.
You can then commit and push your changes at your leisure.</p>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">Symlinks</summary>
  
  A symlink or a Symbolic Link is basically a shortcut to another file. It is a file that points to another file.
</details>

<h2 id="why-manage-dotfiles-with-git">Why manage dotfiles with git?</h2>
<p>One of the main reasons to use VCS (git) to manage your dotfiles is very much the same reason you would use VCS normally.
It allows us to track the history of files, so we can see all the changes made to it. Recently in my case I have been trying different shells.
I prefer to keep my dotfiles clean so when I stop using a shell I delete it. For example I swapped from zsh back to fish.
I moved from zsh to fish shell, and deleted my ZSH config. If I ever need my zsh config back I can trawl through my git
history and retrieve it.</p>
<h2 id="getting-started">Getting started</h2>
<p>To get started with a brand new repository we can use the <a href="https://github.com/Vaelatern/init-dotfiles"><code>init-dotfiles</code> script</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -fsSLO https://raw.githubusercontent.com/Vaelatern/init-dotfiles/master/init_dotfiles.sh
</span></span><span class="line"><span class="cl">chmod +x ./init_dotfiles.sh
</span></span><span class="line"><span class="cl">./init_dotfiles.sh
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Output</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Welcome to the configuration generator <span class="k">for</span> Dotbot
</span></span><span class="line"><span class="cl">Please be aware that <span class="k">if</span> you have a complicated setup, you may need more customization than this script offers.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">At any time, press ^C to quit. No changes will be made <span class="k">until</span> you confirm.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">~/.dotfiles is not in use.
</span></span><span class="line"><span class="cl">Where <span class="k">do</span> you want your dotfiles repository to be? <span class="o">(</span>~/.dotfiles<span class="o">)</span>
</span></span><span class="line"><span class="cl">Shall we add Dotbot as a submodule <span class="o">(</span>a good idea<span class="o">)</span>? <span class="o">(</span>Y/n<span class="o">)</span> y
</span></span><span class="line"><span class="cl">Will <span class="k">do</span>.
</span></span><span class="line"><span class="cl">Do you want Dotbot to clean ~/ of broken links added by Dotbot? <span class="o">(</span>recommended<span class="o">)</span> <span class="o">(</span>Y/n<span class="o">)</span> y
</span></span><span class="line"><span class="cl">I will ask Dotbot to clean.
</span></span><span class="line"><span class="cl">I found ~/.profile, <span class="k">do</span> you want to Dotbot it? <span class="o">(</span>Y/n<span class="o">)</span> y
</span></span><span class="line"><span class="cl">Dotbotted!
</span></span><span class="line"><span class="cl">I found ~/.bashrc, <span class="k">do</span> you want to Dotbot it? <span class="o">(</span>Y/n<span class="o">)</span> y
</span></span><span class="line"><span class="cl">Dotbotted!
</span></span><span class="line"><span class="cl">Shall I make the initial commit? <span class="o">(</span>Y/n<span class="o">)</span> y
</span></span><span class="line"><span class="cl">Will <span class="k">do</span>.
</span></span></code></pre></div><p>This will create an empty dotfiles repo we can then configure, it will look like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">.
</span></span><span class="line"><span class="cl"><span class="p">|</span>-- bashrc
</span></span><span class="line"><span class="cl"><span class="p">|</span>-- dotbot
</span></span><span class="line"><span class="cl"><span class="p">|</span>-- install
</span></span><span class="line"><span class="cl"><span class="p">|</span>-- install.conf.yaml
</span></span><span class="line"><span class="cl"><span class="sb">`</span>-- profile
</span></span></code></pre></div><h3 id="installconfyaml">install.conf.yaml</h3>
<p>The main config file <code>install.conf.yaml</code> is where we configure what files to copy and where to copy them.</p>
<p>The default version of this file will look something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">clean</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s1">&#39;~&#39;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">link</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">~/.profile</span><span class="p">:</span><span class="w"> </span><span class="l">profile</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">~/.bashrc</span><span class="p">:</span><span class="w"> </span><span class="l">bashrc</span><span class="w">
</span></span></span></code></pre></div><p>Let&rsquo;s break down what this file is doing;</p>
<h4 id="clean">clean</h4>
<blockquote>
<p>Clean commands specify directories that should be checked for dead symbolic links. These dead links are removed automatically. Only dead links that point to somewhere within the dotfiles directory are removed unless the force option is set to true - Dotbot</p>
</blockquote>
<p>So this means it will check our home directory (<code>~</code>) for any dead symbolic links and remove them.</p>
<h4 id="link">link</h4>
<p>This is the main part of config file, it tells DotBot where to symlink the files in our dotfiles repo on our systems.
For example, the profile file in the dotfiles repository will be copied to <code>~/.profile</code> location.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">~/.profile</span><span class="p">:</span><span class="w"> </span><span class="l">profile</span><span class="w">
</span></span></span></code></pre></div><p>If you want to copy all the files in a folder you could so something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">link</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">~/.config/fish</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">fish/**</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">glob</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">create</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span></code></pre></div><p>We will copy everything from the <code>fish</code> folder in the dotfiles repo to the <code>~/.config/fish</code> location.</p>
<ul>
<li><code>path</code>: acts as a glob and will copy everything in this folder to the location we specified</li>
<li><code>glob</code>: if set to true will treat path as a glob</li>
<li><code>create</code>: if set to true will create the folders for us if they don&rsquo;t exist, for example <code>~/.config/fish</code> folder</li>
</ul>
<h4 id="shell">shell</h4>
<p>We can also add a shell field where you specify commands to run on the shell. If we want to install starship prompt
we could do something like this.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">shell</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="l">curl -fsSL https://starship.rs/install.sh | sh -s -- --yes</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">stdout</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">stderr</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span></code></pre></div><p>We set stdout and stderr to true so we can see the command output.</p>
<h2 id="how-to-run-it">How to run it?</h2>
<p>To &ldquo;install&rdquo; our dotfiles on our machine run the following command <code>./install</code>.
This wil run all the commands in order of the config file we defined above.</p>
<h2 id="closing-thoughts">Closing Thoughts</h2>
<p>Now that we have a repo locally, we can push it to a remote git repository like Github or Gitlab.
That way if we lose access to our machine we still have our dotfiles backed up and safe.</p>
<p><img
        loading="lazy"
        src="/posts/2022-10-15-how-to-manage-your-dotfiles-with-dotbot/images/dotfiles.jpg"
        type=""
        alt="Dotfiles Meme"
        
      /></p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/dotfiles">My Dotfiles</a></li>
<li><a href="https://github.com/PatentLobster/dotfiles">Doge Dotfiles Meme</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Running Gitlab CI jobs in Docker using docker-compose</title>
      <link>https://haseebmajid.dev/posts/2022-08-08-running-gitlab-ci-jobs-in-docker-using-docker-compose/</link>
      <pubDate>Mon, 08 Aug 2022 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2022-08-08-running-gitlab-ci-jobs-in-docker-using-docker-compose/</guid>
      <description>&lt;p&gt;Shameless plug: This is related to a EuroPython 2022 talk I am giving, &lt;a href=&#34;https://haseebmajid.dev/talks/my-journey-using-docker-as-a-developer-tool&#34;&gt;My Journey Using Docker as a Development Tool&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For most of my common dev tasks, I&amp;rsquo;ve started to rely on &lt;code&gt;docker&lt;/code&gt;/&lt;code&gt;docker compose&lt;/code&gt; to run commands locally. I have also
started using vscode&amp;rsquo;s &lt;code&gt;.devcontainers&lt;/code&gt;, to provide a consistent environment for all developers using a project.&lt;/p&gt;
&lt;p&gt;The main reason for this is to avoid needing to install dependencies on my host machine. In theory, all I
should need is a Docker daemon and a CLI (docker CLI) to interact with that Daemon. This also makes it
far easier for any new developer to start working on my project and get set up.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Shameless plug: This is related to a EuroPython 2022 talk I am giving, <a href="/talks/my-journey-using-docker-as-a-developer-tool">My Journey Using Docker as a Development Tool</a>.</p>
<p>For most of my common dev tasks, I&rsquo;ve started to rely on <code>docker</code>/<code>docker compose</code> to run commands locally. I have also
started using vscode&rsquo;s <code>.devcontainers</code>, to provide a consistent environment for all developers using a project.</p>
<p>The main reason for this is to avoid needing to install dependencies on my host machine. In theory, all I
should need is a Docker daemon and a CLI (docker CLI) to interact with that Daemon. This also makes it
far easier for any new developer to start working on my project and get set up.</p>
<p>What inspired me to do this change now (in my banter bus project) was I wanted to upgrade to
python 3:10 to use some of the new typing features released. However when I tried to upgrade my CI pipeline
started failing, after hours of trying to debug it. I ended up using Docker and everything ran smoothly.</p>
<p>Now to have a more consistent environment between my local environment and CI. So in theory, it means
less chance of something passing locally but failing in CI.</p>
<p>Now we know why we want to do it. let&rsquo;s look at how we do it.</p>
<h2 id="before">Before</h2>
<p>Let&rsquo;s take a look at what a typical CI pipeline may look for a Python project (using banter bus).
In this example, we will be using a FastAPI web service which uses Poetry to manage its dependencies.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">python:3.10.5</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">DOCKER_DRIVER</span><span class="p">:</span><span class="w"> </span><span class="l">overlay2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">PIP_CACHE_DIR</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;${CI_PROJECT_DIR}/.cache/pip&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">PIP_DOWNLOAD_DIR</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;.pip&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">DOCKER_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">tcp://docker:2375</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">cache</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;${CI_JOB_NAME}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">.cache/pip</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">.venv</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">.test</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">mongo:4.4.4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">redis:6.2.4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-message-queue</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">registry.gitlab.com/banter-bus/banter-bus-management-api:test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-management-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">registry.gitlab.com/banter-bus/banter-bus-management-api/database-seed:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database-seed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">MONGO_INITDB_ROOT_USERNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">MONGO_INITDB_ROOT_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">MONGO_INITDB_DATABASE</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_USERNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">27017</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_NAME</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_WEB_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">8090</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_CLIENT_ID</span><span class="p">:</span><span class="w"> </span><span class="l">client_id</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_USE_AUTH</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;False&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">MONGO_HOSTNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database:27017</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_CORE_API_DB_USERNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_CORE_API_DB_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_CORE_API_DB_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_CORE_API_DB_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">27017</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_CORE_API_DB_NAME</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_CORE_API_MANAGEMENT_API_URL</span><span class="p">:</span><span class="w"> </span><span class="l">http://banter-bus-management-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_CORE_API_MANAGEMENT_API_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">8090</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_CORE_API_CLIENT_ID</span><span class="p">:</span><span class="w"> </span><span class="l">client_id</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_CORE_API_USE_AUTH</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;False&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_CORE_API_MESSAGE_QUEUE_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-message-queue</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_CORE_API_MESSAGE_QUEUE_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">6379</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">before_script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">pip download --dest=${PIP_DOWNLOAD_DIR} poetry</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">pip install --find-links=${PIP_DOWNLOAD_DIR} poetry</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">poetry config virtualenvs.in-project true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">poetry install -vv</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">test:lint</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_request</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">poetry run pre-commit run --all-files</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">test:unit-tests</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_request</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">poetry run pytest -v tests/unit</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">test:integration-tests</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_request</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">extends</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">.test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">poetry run pytest -v tests/integration</span><span class="w">
</span></span></span></code></pre></div><p>The above looks quite complicated, but very simply we install our dependencies for each job the <code>before_script</code> section is used in all jobs.
All jobs also use <code>python:3.9.8</code> image, this is where our code is cloned into the CI pipeline.</p>
<p>Where our <code>.pre-commit-config.yaml</code> looks something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">repos</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">repo</span><span class="p">:</span><span class="w"> </span><span class="l">https://github.com/pre-commit/pre-commit-hooks</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">rev</span><span class="p">:</span><span class="w"> </span><span class="l">v3.3.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">hooks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">check-yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">args</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;--allow-multiple-documents&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">repo</span><span class="p">:</span><span class="w"> </span><span class="l">local</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">hooks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">forbidden-files</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">forbidden files</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entry</span><span class="p">:</span><span class="w"> </span><span class="l">found copier update rejection files; review them and remove them</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">language</span><span class="p">:</span><span class="w"> </span><span class="l">fail</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">files</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;\\.rej$&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">black</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">black</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entry</span><span class="p">:</span><span class="w"> </span><span class="l">poetry run black</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">language</span><span class="p">:</span><span class="w"> </span><span class="l">system</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">types</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">python]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">flake8</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">flake8</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entry</span><span class="p">:</span><span class="w"> </span><span class="l">poetry run flake8</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">language</span><span class="p">:</span><span class="w"> </span><span class="l">system</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">types</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">python]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">isort</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">isort</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entry</span><span class="p">:</span><span class="w"> </span><span class="l">poetry run isort --settings-path=.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">language</span><span class="p">:</span><span class="w"> </span><span class="l">system</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">types</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">python]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">pyupgrade</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">pyupgrade</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entry</span><span class="p">:</span><span class="w"> </span><span class="l">poetry run pyupgrade</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">language</span><span class="p">:</span><span class="w"> </span><span class="l">system</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">types</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">python]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">args</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>--<span class="l">py310-plus]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">mypy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">mypy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="l">Check python types.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entry</span><span class="p">:</span><span class="w"> </span><span class="l">poetry run mypy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">language</span><span class="p">:</span><span class="w"> </span><span class="l">system</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">types</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">python]</span><span class="w">
</span></span></span></code></pre></div><p><code>pre-commit</code> is a library we can use to add pre-commit hooks before we commit our code to git. Adding some checks that
the code is consistent with the rules we defined. We can also just use it as a lint job, multiple linting tools together. Simplified. Hence
here we are checking for code formatting, linting, import sorting etc. The details don&rsquo;t matter but at the moment we need to have
a virtualenv locally to run this.</p>
<h3 id="integration-tests">Integration Tests</h3>
<p>A slightly more interesting job is integration tests, it requires other docker containers, as our tests need Postgres and Redis to run.
We can define these as services and then reference them in our job like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">test:integration-tests</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_request</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">extends</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">.test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">poetry run pytest -v tests/integration</span><span class="w">
</span></span></span></code></pre></div><p>Note the <code>extends</code> clause, which essentially merges the <code>.test</code> section with our job so it will look something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">test:integration-tests</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_request</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">mongo:4.4.4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">redis:6.2.4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-message-queue</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">registry.gitlab.com/banter-bus/banter-bus-management-api:test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-management-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">registry.gitlab.com/banter-bus/banter-bus-management-api/database-seed:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database-seed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">MONGO_INITDB_ROOT_USERNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">MONGO_INITDB_ROOT_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">MONGO_INITDB_DATABASE</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_USERNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">27017</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_NAME</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_WEB_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">8090</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_CLIENT_ID</span><span class="p">:</span><span class="w"> </span><span class="l">client_id</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_USE_AUTH</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;False&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">MONGO_HOSTNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database:27017</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_CORE_API_DB_USERNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  	</span><span class="nt">BANTER_BUS_CORE_API_DB_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  	</span><span class="nt">BANTER_BUS_CORE_API_DB_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  	</span><span class="nt">BANTER_BUS_CORE_API_DB_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">27017</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  	</span><span class="nt">BANTER_BUS_CORE_API_DB_NAME</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  	</span><span class="nt">BANTER_BUS_CORE_API_MANAGEMENT_API_URL</span><span class="p">:</span><span class="w"> </span><span class="l">http://banter-bus-management-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  	</span><span class="nt">BANTER_BUS_CORE_API_MANAGEMENT_API_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">8090</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  	</span><span class="nt">BANTER_BUS_CORE_API_CLIENT_ID</span><span class="p">:</span><span class="w"> </span><span class="l">client_id</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  	</span><span class="nt">BANTER_BUS_CORE_API_USE_AUTH</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;False&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  	</span><span class="nt">BANTER_BUS_CORE_API_MESSAGE_QUEUE_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-message-queue</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  	</span><span class="nt">BANTER_BUS_CORE_API_MESSAGE_QUEUE_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">6379</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">poetry run pytest -v tests/integration</span><span class="w">
</span></span></span></code></pre></div><p>We also need to define a bunch of environment variables in this case so our containers can communicate
with each other. Now, these are of course specific to my apps. But you can imagine a real-life project also
needing a bunch of environment variables. As you can see this can get a bit messy and what is
running locally may differ slightly from what is running in CI.</p>
<p>I have been caught out by these env variables in the past. Note variables like
<code>BANTER_BUS_CORE_API_MANAGEMENT_API_URL: http://banter-bus-management-api</code>. The name of the
container must match the URL we have provided</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">registry.gitlab.com/banter-bus/banter-bus-management-api:test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-management-api</span><span class="w">
</span></span></span></code></pre></div><p>Docker DNS (link to DNS) is clever enough to work out the IP address.
This is also different now to how we are running it locally.</p>
<h2 id="after">After</h2>
<p>Now we are running all our dev tasks in docker. We will use docker-compose to manage all of the containers,
docker-compose makes managing multiple containers a lot easier. We define all of them in our
<code>docker-compose.yml</code> file.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">app</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-core-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">build</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">context</span><span class="p">:</span><span class="w"> </span><span class="l">.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">dockerfile</span><span class="p">:</span><span class="w"> </span><span class="l">Dockerfile</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">target</span><span class="p">:</span><span class="w"> </span><span class="l">development</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">cache_from</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">registry.gitlab.com/banter-bus/banter-bus-core-api:development</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">XDG_DATA_HOME</span><span class="p">:</span><span class="w"> </span><span class="l">/commandhistory/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_CORE_API_DB_USERNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_CORE_API_DB_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_CORE_API_DB_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_CORE_API_DB_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">27017</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_CORE_API_DB_NAME</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_CORE_API_MANAGEMENT_API_URL</span><span class="p">:</span><span class="w"> </span><span class="l">http://banter-bus-management-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_CORE_API_MANAGEMENT_API_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">8090</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_CORE_API_CLIENT_ID</span><span class="p">:</span><span class="w"> </span><span class="l">client_id</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_CORE_API_USE_AUTH</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;False&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_CORE_API_MESSAGE_QUEUE_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-message-queue</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_CORE_API_MESSAGE_QUEUE_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">6379</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">127.0.0.1</span><span class="p">:</span><span class="m">8080</span><span class="p">:</span><span class="m">8080</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./:/app</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/app/.venv/</span><span class="w"> </span><span class="c"># This stops local .venv getting mounted</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">management-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">message-queue</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">database-seed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">management-api</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-management-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">registry.gitlab.com/banter-bus/banter-bus-management-api:test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_USERNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_NAME</span><span class="p">:</span><span class="w"> </span><span class="l">banter_bus_management_api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_MANAGEMENT_API_WEB_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">8090</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_MANAGEMENT_API_CLIENT_ID</span><span class="p">:</span><span class="w"> </span><span class="l">client_id</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_MANAGEMENT_API_USE_AUTH</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;False&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">127.0.0.1</span><span class="p">:</span><span class="m">8090</span><span class="p">:</span><span class="m">8090</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">database</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">mongo:4.4.4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">MONGO_INITDB_ROOT_USERNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">MONGO_INITDB_ROOT_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">MONGO_INITDB_DATABASE</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/data/db</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">27017</span><span class="p">:</span><span class="m">27017</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">database-gui</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database-gui</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">mongoclient/mongoclient:4.0.1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">MONGOCLIENT_DEFAULT_CONNECTION_URL=mongodb://banterbus:banterbus@banter-bus-database:27017</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/data/db mongoclient/mongoclient</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">127.0.0.1</span><span class="p">:</span><span class="m">4000</span><span class="p">:</span><span class="m">3000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">database-seed</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database-seed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">registry.gitlab.com/banter-bus/banter-bus-management-api/database-seed:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">MONGO_INITDB_ROOT_USERNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">MONGO_INITDB_ROOT_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">MONGO_INITDB_DATABASE</span><span class="p">:</span><span class="w"> </span><span class="l">banter_bus_management_api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">MONGO_HOSTNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database:27017</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">message-queue</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-message-queue</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">redis:6.2.4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/data/datastore /data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">127.0.0.1</span><span class="p">:</span><span class="m">6379</span><span class="p">:</span><span class="m">6379</span><span class="w">
</span></span></span></code></pre></div><p>Note: This file was already defined just not used in CI because I wanted to provide an easy way to start up my &ldquo;tech stack&rdquo;.
So the file had gone unused.</p>
<p>How do run our dev tasks?</p>
<ul>
<li>lint: <code>docker compose run app poetry run pre-commit run --all-files</code></li>
<li>integration tests: <code>docker compose run app poetry run pytest -v tests/integration</code></li>
</ul>
<p>Then our CI pipelines could look simply like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">docker</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">docker:dind</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">DOCKER_DRIVER</span><span class="p">:</span><span class="w"> </span><span class="l">overlay2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">DOCKER_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">tcp://docker:2375</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">before_script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">docker compose build</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">test:lint</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_request</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">docker compose run app poetry run pre-commit run --all-files</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">test:unit-tests</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_request</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">docker compose run app poetry run pytest -v tests/unit</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">test:integration</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_request</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="w"> </span><span class="l">docker compose run app poetry run pytest -v tests/integration</span><span class="w">
</span></span></span></code></pre></div><p>Now before job we build our docker images, <code>docker compose build</code>.
Then to run the dev task we do something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose run app &lt;<span class="nb">command</span> to run&gt;
</span></span></code></pre></div><p>So to run unit tests we could do:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose run app poetry run pytest -v tests/unit
</span></span></code></pre></div><h3 id="aside">Aside</h3>
<p>We could simplify this if we use <code>makefile</code> and make the target be  <code>poetry run pytest -v tests/unit</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-makefile" data-lang="makefile"><span class="line"><span class="cl"><span class="nf">.PHONY</span><span class="o">:</span> <span class="n">unit_tests</span>
</span></span><span class="line"><span class="cl"><span class="nf">unit_tests</span><span class="o">:</span> <span class="c">## Run all the unit tests
</span></span></span><span class="line"><span class="cl"><span class="c"></span>	@poetry run pytest -v tests/unit
</span></span></code></pre></div><p>Then our ci job would look something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">test:unit-tests</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_request</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">make unit_tests</span><span class="w">
</span></span></span></code></pre></div><p>Which I think is a lot more readable and a lot easier to type. We can also
leverage auto-complete on the terminal and add help targets. So a user can see all the targets
they can run.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>Separate function handler modules when using Python Socketio</title>
      <link>https://haseebmajid.dev/posts/2021-12-31-separate-function-handler-modules-when-using-python-socketio/</link>
      <pubDate>Fri, 31 Dec 2021 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2021-12-31-separate-function-handler-modules-when-using-python-socketio/</guid>
      <description>&lt;p&gt;In this article I will show you how you can have separate modules for your Socketio event handlers.
Rather than keeping them all in the same file.&lt;/p&gt;
&lt;p&gt;Hopefully this should be a relatively short article, lets get into it&lt;/p&gt;
&lt;h2 id=&#34;main&#34;&gt;Main&lt;/h2&gt;
&lt;p&gt;In this example I will be using SocketIO alongside FastAPI, but you can easily change this code to be SocketIO
only. I also will be using a uvicorn to run the server.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this article I will show you how you can have separate modules for your Socketio event handlers.
Rather than keeping them all in the same file.</p>
<p>Hopefully this should be a relatively short article, lets get into it</p>
<h2 id="main">Main</h2>
<p>In this example I will be using SocketIO alongside FastAPI, but you can easily change this code to be SocketIO
only. I also will be using a uvicorn to run the server.</p>
<p>For example</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">fastapi</span> <span class="kn">import</span> <span class="n">FastAPI</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">fastapi_socketio</span> <span class="kn">import</span> <span class="n">SocketManager</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">application</span> <span class="o">=</span> <span class="n">FastAPI</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">&#34;banter-bus-core-api&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">socket_manager</span> <span class="o">=</span> <span class="n">SocketManager</span><span class="p">(</span><span class="n">app</span><span class="o">=</span><span class="n">application</span><span class="p">,</span> <span class="n">mount_location</span><span class="o">=</span><span class="s2">&#34;/&#34;</span><span class="p">)</span>
</span></span></code></pre></div><p>Here is where we setup our FastAPI application and create a Socketio server as a sub-application and mount it.</p>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">fastapi-socketio</summary>
  
  Here I am using the <code>fastapi-socketio</code> library to handle mounting the application into the Fastapi app.
But this again can be done without the library, see this <a href="https://github.com/tiangolo/fastapi/issues/129#issuecomment-547806432">Github issue</a> for an example.
</details>

<p>Anyhow we could simply do something like, this create a Socketio only server without FastAPI.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">socketio</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">sio</span> <span class="o">=</span> <span class="n">socketio</span><span class="o">.</span><span class="n">AsyncServer</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">application</span> <span class="o">=</span> <span class="n">socketio</span><span class="o">.</span><span class="n">ASGIApp</span><span class="p">(</span><span class="n">sio</span><span class="p">)</span>
</span></span></code></pre></div><h2 id="handler-module">Handler Module</h2>
<p>Next lets take a look at the module which will handle our various events it should listen to from the client.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">app.main</span> <span class="kn">import</span> <span class="n">socket_manager</span> <span class="k">as</span> <span class="n">sm</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@sm.on</span><span class="p">(</span><span class="s2">&#34;FOO&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">foo_event</span><span class="p">(</span><span class="n">sid</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">sm</span><span class="o">.</span><span class="n">emit</span><span class="p">(</span><span class="s2">&#34;BAR&#34;</span><span class="p">,</span> <span class="p">{</span><span class="s2">&#34;response&#34;</span><span class="p">:</span> <span class="s2">&#34;hello world!&#34;</span><span class="p">})</span>
</span></span></code></pre></div><p>As you can see this handler imports the socket manager object in the second example this would be the object called <code> sio</code>. Then decorates a function, this function then will be called everytime a client sends an <code>FOO</code> event
to our web server. In this example it returns a <code>BAR</code> event (emits it) with <code>hello world</code>.
What this function does specifically doesn&rsquo;t really matter.</p>
<h2 id="initpy"><strong>init</strong>.py</h2>
<p>Finally let&rsquo;s put all this in our <code>app/__init__.py</code> module:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">uvicorn</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">app.foo.foo_handlers</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">app.main</span> <span class="kn">import</span> <span class="n">application</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">application</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">uvicorn</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">app</span><span class="p">,</span> <span class="n">host</span><span class="o">=</span><span class="s2">&#34;0.0.0.0&#34;</span><span class="p">,</span> <span class="n">port</span><span class="o">=</span><span class="mi">8080</span><span class="p">)</span>
</span></span></code></pre></div><p>This may look a bit confusing, essentially this is the module that uvicorn will call directly to start the server. When then import our function handlers <code>import app.foo.foo_handlers</code>, here you will need to import
all of your function handlers, even though they aren&rsquo;t used here. So this is so that your application knows
they exist.</p>
<p>Without this import your application will have no way to &ldquo;attach&rdquo; them to the app. Now everytime the <code>FOO</code>
event is emitted from a client, your server knows to send it that function.</p>
<p>Finally we create a simple dummy variable <code>app = application</code> where application is the FastAPI/ASGIApp we
created in the <code>main.py</code> module. You could leave this as application but usually when using uvicorn
, i.e. looking at examples it will use <code>app</code>. So hence I&rsquo;ve renamed it here.</p>
<p>The final block is not really needed:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">uvicorn</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">app</span><span class="p">,</span> <span class="n">host</span><span class="o">=</span><span class="s2">&#34;0.0.0.0&#34;</span><span class="p">,</span> <span class="n">port</span><span class="o">=</span><span class="mi">8080</span><span class="p">)</span>
</span></span></code></pre></div><p>It more exists if this module is used as a main file and will start the uvicorn server for us. However typically I will start the uvicorn server myself, usually in my Docker images or launch.json (VSCode debugger) config etc.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">uvicorn app:app --host <span class="s2">&#34;0.0.0.0&#34;</span> --port <span class="m">8080</span>
</span></span></code></pre></div><h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/banter-bus/banter-bus-core-api">Example project using this pattern</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>E2E tests with Gitlab CI services</title>
      <link>https://haseebmajid.dev/posts/2021-12-25-e2e-tests-with-gitlab-ci-services/</link>
      <pubDate>Sat, 25 Dec 2021 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2021-12-25-e2e-tests-with-gitlab-ci-services/</guid>
      <description>&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;p&gt;This will be a slightly shorter article. In this article I will show you how I&amp;rsquo;ve managed to do some
end-to-end testing with Gitlab CI services.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m building a browser-based multiplayer game called Banter Bus. Banter Bus consists of three main components,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;gui: A SvelteKit based frontend the user will interact with to play the game&lt;/li&gt;
&lt;li&gt;core-api: A Socketio API written in Python&lt;/li&gt;
&lt;li&gt;management-api: A simple RESTful API written in Python (FastAPI)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now say I want to write some e2e Cypress tests, that will test all of these components interacting with each other.
Which mainly will look something like &lt;code&gt;gui -&amp;gt; core-api -&amp;gt; management-api&lt;/code&gt;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="background">Background</h2>
<p>This will be a slightly shorter article. In this article I will show you how I&rsquo;ve managed to do some
end-to-end testing with Gitlab CI services.</p>
<p>I&rsquo;m building a browser-based multiplayer game called Banter Bus. Banter Bus consists of three main components,</p>
<ul>
<li>gui: A SvelteKit based frontend the user will interact with to play the game</li>
<li>core-api: A Socketio API written in Python</li>
<li>management-api: A simple RESTful API written in Python (FastAPI)</li>
</ul>
<p>Now say I want to write some e2e Cypress tests, that will test all of these components interacting with each other.
Which mainly will look something like <code>gui -&gt; core-api -&gt; management-api</code>.</p>
<p>Each of these project deploys its own Docker container, which we can then use for testing it. So how can we do this with Gitlab CI ?</p>
<h2 id="gitlab-services">Gitlab Services</h2>
<p>What is a Gitlab CI service ?</p>
<blockquote>
<p>The services keyword defines a Docker image that runs during a job linked to the Docker image that the image keyword defines. This allows you to access the service image during build time. - <a href="https://docs.gitlab.com/ee/ci/services/">https://docs.gitlab.com/ee/ci/services/</a></p>
</blockquote>
<p>Essentially they are Docker containers we can use in our CI jobs.</p>
<h2 id="packagejson">package.json</h2>
<p>For the examples below assume our <code>package.json</code> scipts section looks something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;dev&#34;</span><span class="p">:</span> <span class="s2">&#34;svelte-kit dev&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;e2e&#34;</span><span class="p">:</span> <span class="s2">&#34;cypress run --browser chrome&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;e2e:ci&#34;</span><span class="p">:</span> <span class="s2">&#34;start-server-and-test dev http://localhost:3000 e2e&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h2 id="gitlab-ci">Gitlab CI</h2>
<p>Let&rsquo;s take a look at an example <code>.gitlab-ci.yml</code> file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">cypress-e2e-chrome</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">cypress/browsers:node14.17.0-chrome88-ff89</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nt">BANTER_BUS_CORE_API_MANAGEMENT_API_URL</span><span class="p">:</span><span class="w"> </span><span class="l">http://banter-bus-management-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nt">BANTER_BUS_CORE_API_DB_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nt">FF_NETWORK_PER_BUILD</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c"># Hidden the rest of the variables as not to clutter the file</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">mongo:4.4.4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">registry.gitlab.com/banter-bus/banter-bus-core-api:test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-core-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">registry.gitlab.com/banter-bus/banter-bus-management-api:test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-management-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">registry.gitlab.com/banter-bus/banter-bus-management-api/database-seed:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database-seed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span>- <span class="l">npm ci</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">export VITE_BANTER_BUS_CORE_API_URL=http://banter-bus-core-api:8080</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">echo fs.inotify.max_user_watches=524288 | tee -a /etc/sysctl.conf &amp;&amp; sysctl -p</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">npm run e2e:ci</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">artifacts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">expire_in</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w"> </span><span class="l">week</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">when</span><span class="p">:</span><span class="w"> </span><span class="l">always</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">cypress/screenshots</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">cypress/videos</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">reports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">junit</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">results/TEST-*.xml</span><span class="w">
</span></span></span></code></pre></div><h3 id="services">Services</h3>
<p>Let&rsquo;s break file down a bit, these are essentially all the dependencies of our <code>gui</code> application. We need all of
these containers running.</p>
<p>In this case we need four containers (this doesn&rsquo;t really matter):</p>
<ul>
<li>banter-bus-database: A database for the core-api and management-api</li>
<li>banter-bus-core-api: The main API the gui will interact with</li>
<li>banter-bus-management-api: Used to help manage our available games, questions etc</li>
<li>banter-bus-database-seed: A short lived container which pre-fills the database with some values</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">mongo:4.4.4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">registry.gitlab.com/banter-bus/banter-bus-core-api:test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-core-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">registry.gitlab.com/banter-bus/banter-bus-management-api:test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-management-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">registry.gitlab.com/banter-bus/banter-bus-management-api/database-seed:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database-seed</span><span class="w">
</span></span></span></code></pre></div><p>In our examples the <code>name</code> field is the image name, this is the same name you&rsquo;d use when using the <code>docker pull</code>
command. The next field is <code>alias</code> this is the name we&rsquo;ll use to reference that container. This is the container name.</p>
<p>To see how the <code>alias</code> is used, is to look at the environment variables we have provided for the job
<code>BANTER_BUS_CORE_API_DB_HOST: banter-bus-database</code>. So core-api will try to connect to database using
this host. You can read more about Docker is able to resolve this to an <a href="/blog/dns-docker-explained/">IP address here</a>. Another example is how the URL core-api will use to connect to the management-api
<code>BANTER_BUS_CORE_API_MANAGEMENT_API_URL: http://banter-bus-management-api</code>.</p>
<details
  class="notice danger"
  open="true"
>
    <summary class="notice-title">ENV Variable</summary>
  
  One environment variable we must provide is <code>FF_NETWORK_PER_BUILD</code> set to <code>1</code> (or true). Docker then
creates a bridge network so all the services can communicate amognst themselves. You can read more about
<a href="https://docs.gitlab.com/runner/executors/docker.html#create-a-network-for-each-job">it here</a>
</details>

<p>We&rsquo;ve discussed the most important part of the CI file, but lets quickly discuss the rest for completeness</p>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">Optional</summary>
  
  I&rsquo;ve discussed the main point of this article, how to use services and how to get them to work together.
</details>

<h3 id="variables">Variables</h3>
<p>We&rsquo;ve already spoken about this above, but lets take a quick look at the <code>variables</code> section.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">BANTER_BUS_CORE_API_MANAGEMENT_API_URL</span><span class="p">:</span><span class="w"> </span><span class="l">http://banter-bus-management-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">BANTER_BUS_CORE_API_DB_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">FF_NETWORK_PER_BUILD</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># Hidden the rest of the variables as not to clutter the f</span><span class="w">
</span></span></span></code></pre></div><p>These are environment variables that are shared both with the job and the services. Some of these are
config passed to the application, such as <code>BANTER_BUS_CORE_API_MANAGEMENT_API_URL</code> and <code>BANTER_BUS_CORE_API_DB_HOST</code>.</p>
<h3 id="script">Script</h3>
<p>Since we are using the <code>cypress/browsers:node14.17.0-chrome88-ff89</code> image, we have access to chrome
(headless) browser we can use with Cypress.</p>
<p>So we can do something like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">npm ci</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">export VITE_BANTER_BUS_CORE_API_URL=http://banter-bus-core-api:8080</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">npm run e2e:ci</span><span class="w">
</span></span></span></code></pre></div><ul>
<li><code>npm ci</code>: Installs our npm dependencies for the gui app</li>
<li><code>export VITE_BANTER_BUS_CORE_API_URL=http://banter-bus-core-api:8080</code> exports an enviroment variable which will be used by the gui app so it knows the URL of the core-api. Note the use of the alias name here (and port <code>:8080</code> default port for the core-api)</li>
<li><code>npm run e2e:ci</code>: Starts the dev server and then runs the cypress test, see <code>start-server-and-test dev http://localhost:3000 e2e</code> where <code>e2e</code> is <code>cypress run --browser chrome</code></li>
</ul>
<h3 id="artifacts">Artifacts</h3>
<p>Finally, the artifacts are &ldquo;things&rdquo; that are left over after the build. In this case we use them in two ways:</p>
<ul>
<li>One to generate a coverage report with <code>junit</code></li>
<li>Two to save our Cypress screenshots and videos</li>
</ul>
<p>The downloadable artifacts will expire after 1 week. The Cypress files can be useful when debugging a problem
with your tests. You get a video of perhaps why the tests failed.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">artifacts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">expire_in</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w"> </span><span class="l">week</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">when</span><span class="p">:</span><span class="w"> </span><span class="l">always</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">cypress/screenshots</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">cypress/videos</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">reports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">junit</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">results/TEST-*.xml</span><span class="w">
</span></span></span></code></pre></div><h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/banter-bus/banter-bus-gui/-/tree/350f1f986b077ac86da924b830fed88ffcd3cde0">Example Project</a></li>
<li><a href="https://gitlab.com/banter-bus/banter-bus-gui/-/jobs/1920396599">Example Job</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Testing a socketio Web App written in Python</title>
      <link>https://haseebmajid.dev/posts/2021-12-23-testing-a-socketio-web-app-written-in-python/</link>
      <pubDate>Thu, 23 Dec 2021 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2021-12-23-testing-a-socketio-web-app-written-in-python/</guid>
      <description>&lt;p&gt;In this article I will show you how you can test an async Socketio application in Python, where the ASGI server we are running is uvicorn.
I will be referring to these tests as integration tests, though depending on who you ask they could be called E2E tests, system tests, slow test etc.
What I am referring to is simply testing out the entire &amp;ldquo;flow&amp;rdquo; of a socketio event i.e. emitting an event from a client, then receiving it on the web service
and for my actual projects interacting with an actual database.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this article I will show you how you can test an async Socketio application in Python, where the ASGI server we are running is uvicorn.
I will be referring to these tests as integration tests, though depending on who you ask they could be called E2E tests, system tests, slow test etc.
What I am referring to is simply testing out the entire &ldquo;flow&rdquo; of a socketio event i.e. emitting an event from a client, then receiving it on the web service
and for my actual projects interacting with an actual database.</p>
<p>We will be using <code>pytest</code> as our testing framework.</p>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">ASGI</summary>
  
  ASGI (Asynchronous Server Gateway Interface) is a spiritual successor to WSGI, intended to provide a standard interface between async-capable Python web servers, frameworks, and applications. - <a href="https://asgi.readthedocs.io/en/latest/">https://asgi.readthedocs.io/en/latest/</a>
</details>

<h2 id="mainpy">main.py</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">socketio</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">uvicorn</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">pydantic</span> <span class="kn">import</span> <span class="n">BaseModel</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">startup</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Starting Application&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">sio</span> <span class="o">=</span> <span class="n">socketio</span><span class="o">.</span><span class="n">AsyncServer</span><span class="p">(</span><span class="n">async_mode</span><span class="o">=</span><span class="s2">&#34;asgi&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">socketio</span><span class="o">.</span><span class="n">ASGIApp</span><span class="p">(</span><span class="n">sio</span><span class="p">,</span> <span class="n">on_startup</span><span class="o">=</span><span class="n">startup</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">FooEvent</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">name</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@sio.on</span><span class="p">(</span><span class="s2">&#34;FOO&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">foo_event</span><span class="p">(</span><span class="n">sid</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span> <span class="o">=</span> <span class="n">FooEvent</span><span class="p">(</span><span class="o">**</span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">sio</span><span class="o">.</span><span class="n">emit</span><span class="p">(</span><span class="s2">&#34;BAR&#34;</span><span class="p">,</span> <span class="p">{</span><span class="s2">&#34;foo&#34;</span><span class="p">:</span> <span class="n">data</span><span class="o">.</span><span class="n">name</span><span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">uvicorn</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">app</span><span class="p">,</span> <span class="n">host</span><span class="o">=</span><span class="s2">&#34;0.0.0.0&#34;</span><span class="p">,</span> <span class="n">port</span><span class="o">=</span><span class="mi">8080</span><span class="p">)</span>
</span></span></code></pre></div><p>Let&rsquo;s take a look at our socketio app. Which is a very simple web app, that listens to one event <code>FOO</code> and
responds with a <code>BAR</code> event. It is just this single file.</p>
<h2 id="conftestpy">conftest.py</h2>
<p>The <code>conftest.py</code> file is automatically run by pytest and allows our test modules to access fixtures defined
in this file. One of the best features of Pytest is fixtures. Fixture are functions that have re-usable bits of code we
can run in our tests, such as static data used by tests.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Any</span><span class="p">,</span> <span class="n">AsyncIterator</span><span class="p">,</span> <span class="n">Awaitable</span><span class="p">,</span> <span class="n">List</span><span class="p">,</span> <span class="n">Optional</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pytest</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">socketio</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">uvicorn</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">app</span> <span class="kn">import</span> <span class="n">main</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">socketio</span> <span class="kn">import</span> <span class="n">ASGIApp</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">socketio.asyncio_client</span> <span class="kn">import</span> <span class="n">AsyncClient</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">PORT</span> <span class="o">=</span> <span class="mi">8000</span>
</span></span><span class="line"><span class="cl"><span class="n">LISTENING_IF</span> <span class="o">=</span> <span class="s2">&#34;127.0.0.1&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">BASE_URL</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;http://</span><span class="si">{</span><span class="n">LISTENING_IF</span><span class="si">}</span><span class="s2">:</span><span class="si">{</span><span class="n">PORT</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">UvicornTestServer</span><span class="p">(</span><span class="n">uvicorn</span><span class="o">.</span><span class="n">Server</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">app</span><span class="p">:</span> <span class="n">ASGIApp</span> <span class="o">=</span> <span class="n">main</span><span class="o">.</span><span class="n">app</span><span class="p">,</span> <span class="n">host</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">LISTENING_IF</span><span class="p">,</span> <span class="n">port</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="n">PORT</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_startup_done</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">Event</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_serve_task</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Awaitable</span><span class="p">[</span><span class="n">Any</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">config</span><span class="o">=</span><span class="n">uvicorn</span><span class="o">.</span><span class="n">Config</span><span class="p">(</span><span class="n">app</span><span class="p">,</span> <span class="n">host</span><span class="o">=</span><span class="n">host</span><span class="p">,</span> <span class="n">port</span><span class="o">=</span><span class="n">port</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">startup</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Override uvicorn startup&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="k">await</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">startup</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">setup_event_loop</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_startup_done</span><span class="o">.</span><span class="n">set</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">start_up</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Start up server asynchronously&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_serve_task</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">create_task</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">serve</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">        <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">_startup_done</span><span class="o">.</span><span class="n">wait</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">tear_down</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Shut down server asynchronously&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">should_exit</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_serve_task</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">_serve_task</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span><span class="p">(</span><span class="n">scope</span><span class="o">=</span><span class="s2">&#34;session&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">event_loop</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_event_loop</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">yield</span> <span class="n">loop</span>
</span></span><span class="line"><span class="cl">    <span class="n">loop</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span><span class="p">(</span><span class="n">autouse</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">scope</span><span class="o">=</span><span class="s2">&#34;session&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">startup_and_shutdown_server</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">server</span> <span class="o">=</span> <span class="n">UvicornTestServer</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">server</span><span class="o">.</span><span class="n">start_up</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">yield</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">server</span><span class="o">.</span><span class="n">tear_down</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span><span class="p">(</span><span class="n">scope</span><span class="o">=</span><span class="s2">&#34;session&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">client</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="n">AsyncIterator</span><span class="p">[</span><span class="n">AsyncClient</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">    <span class="n">sio</span> <span class="o">=</span> <span class="n">socketio</span><span class="o">.</span><span class="n">AsyncClient</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">sio</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="n">BASE_URL</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">yield</span> <span class="n">sio</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">sio</span><span class="o">.</span><span class="n">disconnect</span><span class="p">()</span>
</span></span></code></pre></div><h3 id="quick-aside-fastapi-testing">Quick Aside FastAPI Testing</h3>
<details
  class="notice danger"
  open="true"
>
    <summary class="notice-title">Uvicorn</summary>
  
  tl:dr: We need to start and stop the Uvicorn server within our tests.
</details>

<p>Now when testing say a FastAPI application, it has a builtin test client we can use. This means we don&rsquo;t actually have
to spin up a Uvicorn server to test our application. We can simply pretend to send requests to the FastAPI web service
and it will handle the routing behind the scenes.</p>
<p>We can do something like this, where <code>httpx</code> is a async HTTP client (think like the <code>requests</code> library).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pytest</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">asgi_lifespan</span> <span class="kn">import</span> <span class="n">LifespanManager</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">httpx</span> <span class="kn">import</span> <span class="n">AsyncClient</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">app.main</span> <span class="kn">import</span> <span class="n">app</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">client</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="n">AsyncIterator</span><span class="p">[</span><span class="n">AsyncClient</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">    <span class="k">async</span> <span class="k">with</span> <span class="n">LifespanManager</span><span class="p">(</span><span class="n">app</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">async</span> <span class="k">with</span> <span class="n">AsyncClient</span><span class="p">(</span><span class="n">app</span><span class="o">=</span><span class="n">app</span><span class="p">,</span> <span class="n">base_url</span><span class="o">=</span><span class="s2">&#34;http://localhost&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">client</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">yield</span> <span class="n">client</span>
</span></span></code></pre></div><p>Then we can use it like so in our tests:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">fastapi</span> <span class="kn">import</span> <span class="n">status</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">httpx</span> <span class="kn">import</span> <span class="n">AsyncClient</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@pytest.mark.asyncio</span>
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">test_add_game</span><span class="p">(</span><span class="n">client</span><span class="p">:</span> <span class="n">AsyncClient</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="k">await</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s2">&#34;/game&#34;</span><span class="p">,</span> <span class="n">json</span><span class="o">=</span><span class="n">request_data</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="n">status</span><span class="o">.</span><span class="n">HTTP_201_CREATED</span>
</span></span></code></pre></div><p>However socketio at the moment does not provide us with a test client we can use. So we will start and stop a Uvicorn server and send actual
Socketio requests from a Socketio client. There is a Socketio client library we can use to do this, available in Python.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">UvicornTestServer</span><span class="p">(</span><span class="n">uvicorn</span><span class="o">.</span><span class="n">Server</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">app</span><span class="p">:</span> <span class="n">ASGIApp</span> <span class="o">=</span> <span class="n">main</span><span class="o">.</span><span class="n">app</span><span class="p">,</span> <span class="n">host</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">LISTENING_IF</span><span class="p">,</span> <span class="n">port</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="n">PORT</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_startup_done</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">Event</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_serve_task</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Awaitable</span><span class="p">[</span><span class="n">Any</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">config</span><span class="o">=</span><span class="n">uvicorn</span><span class="o">.</span><span class="n">Config</span><span class="p">(</span><span class="n">app</span><span class="p">,</span> <span class="n">host</span><span class="o">=</span><span class="n">host</span><span class="p">,</span> <span class="n">port</span><span class="o">=</span><span class="n">port</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">startup</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Override uvicorn startup&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="k">await</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">startup</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">setup_event_loop</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_startup_done</span><span class="o">.</span><span class="n">set</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">start_up</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Start up server asynchronously&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">_serve_task</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">create_task</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">serve</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">        <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">_startup_done</span><span class="o">.</span><span class="n">wait</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">tear_down</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Shut down server asynchronously&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">should_exit</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_serve_task</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">_serve_task</span>
</span></span></code></pre></div><p>This is a test class which we can use to start and stop the Uvicorn server. Note that the class inherits
from <code>uvicorn.server</code>, we need to overwrite the <code>startup()</code> method as we want to change the startup a bit.</p>
<p>Before explaining the code above let&rsquo;s take a look at how we may use it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span><span class="p">(</span><span class="n">scope</span><span class="o">=</span><span class="s2">&#34;session&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">event_loop</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_event_loop</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">yield</span> <span class="n">loop</span>
</span></span><span class="line"><span class="cl">    <span class="n">loop</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span><span class="p">(</span><span class="n">autouse</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">scope</span><span class="o">=</span><span class="s2">&#34;session&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">startup_and_shutdown_server</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">server</span> <span class="o">=</span> <span class="n">UvicornTestServer</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">server</span><span class="o">.</span><span class="n">start_up</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">yield</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">server</span><span class="o">.</span><span class="n">tear_down</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span><span class="p">(</span><span class="n">scope</span><span class="o">=</span><span class="s2">&#34;session&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">client</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="n">AsyncIterator</span><span class="p">[</span><span class="n">AsyncClient</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">    <span class="n">sio</span> <span class="o">=</span> <span class="n">socketio</span><span class="o">.</span><span class="n">AsyncClient</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">sio</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="n">BASE_URL</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">yield</span> <span class="n">sio</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">sio</span><span class="o">.</span><span class="n">disconnect</span><span class="p">()</span>
</span></span></code></pre></div><p>What we have done is created two pytest fixtures, the first simply starts an event loop so we can test async code.</p>
<h3 id="tangent-on-asyncio">Tangent on asyncio</h3>
<p>To test async code with pytest we need to install the <code>pyest-asyncio</code> library.
By default this will give us an <code>event_loop</code> fixture that runs on scope of <code>function</code>. So it will start and stop after
each test function. However if you want to use fixtures that aren&rsquo;t of scope <code>function</code> i.e. <code>session</code> or <code>module</code>.
Then we need to redefine the <code>event_loop</code> function as we have done in the example above.</p>
<p>Okay back to our code above. The main bit we are interested in is the <code>startup_and_shutdown_server</code> function, here we
start the server before all of our tests and due to how <code>yield</code>, you can read more about how
<a href="/blog/python-yield-explained/">yield works here</a>, we will stop our server after all of our tests have run.</p>
<p>This happens automatically without calling the function because of the decorator we have provided
<code>@pytest.fixture(autouse=True, scope=&quot;session&quot;)</code>.
Again we are using scope <code>session</code> so that this function isn&rsquo;t called either for
every function (which would slow down our tests). We could&rsquo;ve set it to <code>module</code> but again
if we have multiple test files we don&rsquo;t want to run this function for every file (module).</p>
<h3 id="deeper-diver-into-uvicorntestserver">Deeper diver into UvicornTestServer</h3>
<p>Let&rsquo;s take a look at the first two methods</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl">  <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">app</span><span class="p">:</span> <span class="n">ASGIApp</span> <span class="o">=</span> <span class="n">main</span><span class="o">.</span><span class="n">app</span><span class="p">,</span> <span class="n">host</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">LISTENING_IF</span><span class="p">,</span> <span class="n">port</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="n">PORT</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">      <span class="bp">self</span><span class="o">.</span><span class="n">_startup_done</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">Event</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">      <span class="bp">self</span><span class="o">.</span><span class="n">_serve_task</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Awaitable</span><span class="p">[</span><span class="n">Any</span><span class="p">]]</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">      <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">config</span><span class="o">=</span><span class="n">uvicorn</span><span class="o">.</span><span class="n">Config</span><span class="p">(</span><span class="n">app</span><span class="p">,</span> <span class="n">host</span><span class="o">=</span><span class="n">host</span><span class="p">,</span> <span class="n">port</span><span class="o">=</span><span class="n">port</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">async</span> <span class="k">def</span> <span class="nf">startup</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;&#34;&#34;Override uvicorn startup&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="k">await</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">startup</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">      <span class="bp">self</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">setup_event_loop</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">      <span class="bp">self</span><span class="o">.</span><span class="n">_startup_done</span><span class="o">.</span><span class="n">set</span><span class="p">()</span>
</span></span></code></pre></div><p>The <code>__init__</code> magic dunder method creates an asyncio event <code>asyncio.Event()</code>. These events are often used to:</p>
<blockquote>
<p>An asyncio event can be used to notify multiple asyncio tasks that some event has happened. - <a href="https://docs.python.org/3/library/asyncio-sync.html#asyncio.Event">https://docs.python.org/3/library/asyncio-sync.html#asyncio.Event</a></p>
</blockquote>
<p>Then we create a variable <code>self._serve_task: Optional[Awaitable[Any]] = None</code>, we will see how this used later.
Finally we call the parent calls <code>__init__</code> method (<code>super().__init__()</code>). This calls the <code>__init__</code> function
of the <code>uvicorn.Server</code> class. We do this to set the <code>uvicorn.Config</code>, which includes our app and which host and port
to start the server.</p>
<p>Onto the second method <code>startup</code> this also overwrites a method in the parent class. In fact the first we do is call
the parent class&rsquo;s <code>startup</code> method (<code>await super().startup()</code>). Then we start the event loop ourselves
<code>self.config.setup_event_loop()</code>, where our web app will run.</p>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">Event Loop</summary>
  
  This is a different event loop in which our tests run in.
</details>

<p>Finally we do <code>self._startup_done.set()</code>, we are setting this event as true i.e. is complete. So any coroutines waiting
until this set can be carry on their execution.</p>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">Asyncio</summary>
  
  An Event object manages an internal flag that can be set to true with the set() method and reset to false with the clear() method. The wait() method blocks until the flag is set to true. The flag is set to false initially. - <a href="https://docs.python.org/3/library/asyncio-sync.html#asyncio.Event">https://docs.python.org/3/library/asyncio-sync.html#asyncio.Event</a>
</details>

<h4 id="yet-another-tangent-on-run-method">Yet another tangent on run() method</h4>
<p>Now the parent class does have a <code>run</code> method we could use, which would start the event loop for us. This however won&rsquo;t work,
lets pretend we change <code>startup_and_shutdown_server</code> function too look like this (<code>server.run()</code>).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span><span class="p">(</span><span class="n">autouse</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">scope</span><span class="o">=</span><span class="s2">&#34;session&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">startup_and_shutdown_server</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">server</span> <span class="o">=</span> <span class="n">UvicornTestServer</span><span class="p">()</span>
</span></span><span class="line hl"><span class="cl">    <span class="k">await</span> <span class="n">server</span><span class="o">.</span><span class="n">run</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">yield</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">server</span><span class="o">.</span><span class="n">tear_down</span><span class="p">()</span>
</span></span></code></pre></div><p>We would get the following error <code>RuntimeError: asyncio.run() cannot be called from a running event loop</code>. This because if
we take a look at the <code>run</code> method in the parent class it contains something like this line
<code>return asyncio.run(self.serve(...))</code>.</p>
<p>This is why we need to write our own code to handle starting the Uvicorn server.</p>
<h4 id="start_up-and-tear_down"><code>start_up</code> and <code>tear_down</code></h4>
<p>Okay let&rsquo;s move and take a look at the <code>start_up</code> and <code>tear_down</code> methods</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">start_up</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="bp">self</span><span class="o">.</span><span class="n">_serve_task</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">create_task</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">serve</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">_startup_done</span><span class="o">.</span><span class="n">wait</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">tear_down</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="bp">self</span><span class="o">.</span><span class="n">should_exit</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">_serve_task</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">_serve_task</span>
</span></span></code></pre></div><p>Remember these are the two methods we will call in our &ldquo;startup and shutdown&rdquo; fixture. The <code>start_up</code> method, creates a task and assigns it
to our empty variable from the <code>__init__</code> method <code>self._serve_task = asyncio.create_task(self.serve())</code>. It calls the <code>serve</code> method to start
the Uvicorn server.</p>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">What does create_task do?</summary>
  
  It submits the coroutine to run &ldquo;in the background&rdquo;, i.e. concurrently with the current task and all other tasks, switching between them at await points. It returns an awaitable handle called a &ldquo;task&rdquo; which you can also use to cancel the execution of the coroutine. - <a href="https://stackoverflow.com/questions/62528272/what-does-asyncio-create-task-do">https://stackoverflow.com/questions/62528272/what-does-asyncio-create-task-do</a>
</details>

<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">What is a task?</summary>
  
  It&rsquo;s an asyncio construct that tracks execution of a coroutine in a concrete event loop. When you call create_task, you submit a coroutine for execution and receive back a handle. You can await this handle when you actually need the result, or you can never await it, if you don&rsquo;t care about the result. This handle is the task, and it inherits from Future, which makes it awaitable and also provides the lower-level callback-based interface, such as add_done_callback. - <a href="https://stackoverflow.com/questions/62528272/what-does-asyncio-create-task-do">https://stackoverflow.com/questions/62528272/what-does-asyncio-create-task-do</a>
</details>

<p>Then we <code>await self._startup_done.wait()</code>, this is the event we created earlier. It will wait until the <code>set()</code> function
has been called in the in the <code>startup</code> method above.</p>
<p>Now onto the <code>tear_down</code> method where we set the <code>should_exit</code> to true. There is a <code>main_loop</code> method called by our
<code>serve</code> method in the parent class. This <code>main_loop</code> calls an <code>on_tick</code> function which returns if <code>self.should_exit</code> is true.
So the call chain looks like: <code>serve</code> -&gt; <code>main_loop</code> -&gt; <code>on_tick</code>. When on_tick returns <code>should_exist</code> as true, it exits it main loop:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">main_loop</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">counter</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line hl"><span class="cl">    <span class="n">should_exit</span> <span class="o">=</span> <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">on_tick</span><span class="p">(</span><span class="n">counter</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">while</span> <span class="ow">not</span> <span class="n">should_exit</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">counter</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">        <span class="n">counter</span> <span class="o">=</span> <span class="n">counter</span> <span class="o">%</span> <span class="mi">864000</span>
</span></span><span class="line"><span class="cl">        <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
</span></span><span class="line hl"><span class="cl">        <span class="n">should_exit</span> <span class="o">=</span> <span class="k">await</span> <span class="bp">self</span><span class="o">.</span><span class="n">on_tick</span><span class="p">(</span><span class="n">counter</span><span class="p">)</span>
</span></span></code></pre></div><h3 id="client-fixture">Client Fixture</h3>
<p>Finally lets take a look at our final fixture, here we create a client that can be used to make requests with socketio.
We use a similar technique with <code>yields</code> so we return a socketio client. We will see how this used in one of our tests.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span><span class="p">(</span><span class="n">scope</span><span class="o">=</span><span class="s2">&#34;session&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">client</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="n">AsyncIterator</span><span class="p">[</span><span class="n">AsyncClient</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">    <span class="n">sio</span> <span class="o">=</span> <span class="n">socketio</span><span class="o">.</span><span class="n">AsyncClient</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">sio</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="n">BASE_URL</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">yield</span> <span class="n">sio</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">sio</span><span class="o">.</span><span class="n">disconnect</span><span class="p">()</span>
</span></span></code></pre></div><h2 id="test_roompy">test_room.py</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">asyncio</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pytest</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">socketio.asyncio_client</span> <span class="kn">import</span> <span class="n">AsyncClient</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@pytest.mark.asyncio</span>
</span></span><span class="line"><span class="cl"><span class="k">async</span> <span class="k">def</span> <span class="nf">test_success</span><span class="p">(</span><span class="n">client</span><span class="p">:</span> <span class="n">AsyncClient</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">future</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_running_loop</span><span class="p">()</span><span class="o">.</span><span class="n">create_future</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nd">@client.on</span><span class="p">(</span><span class="s2">&#34;BAR&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">_</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">future</span><span class="o">.</span><span class="n">set_result</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">client</span><span class="o">.</span><span class="n">emit</span><span class="p">(</span><span class="s2">&#34;FOO&#34;</span><span class="p">,</span> <span class="p">{</span><span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;haseeb&#34;</span><span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">wait_for</span><span class="p">(</span><span class="n">future</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mf">5.0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="n">result</span> <span class="o">==</span> <span class="p">{</span><span class="s2">&#34;foo&#34;</span><span class="p">:</span> <span class="s2">&#34;haseeb&#34;</span><span class="p">}</span>
</span></span></code></pre></div><p>Since we need to wait for the <code>FOO</code> event to return a <code>BAR</code> event we use a future
to await until we get a response then set the return data in the future</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nd">@client.on</span><span class="p">(</span><span class="s2">&#34;BAR&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">_</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">future</span><span class="o">.</span><span class="n">set_result</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
</span></span></code></pre></div><p>We <code>await asyncio.wait_for(future, timeout=5.0)</code> for the future to have data set on it.</p>
<p>That&rsquo;s it, the code itself is fairly simple once everything is setup in <code>conftest</code> to actually do the test.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2021-12-23-testing-a-socketio-web-app-written-in-python/source_code">Example source code</a></li>
<li><a href="https://github.com/miguelgrinberg/python-socketio/issues/332#issuecomment-712928157">Github Issue: UvicornTestServer</a></li>
<li><a href="https://stackoverflow.com/questions/62528272/what-does-asyncio-create-task-do">Async Create Task SO</a></li>
<li><a href="https://gitlab.com/banter-bus/banter-bus-core-api">Real application using this testing pattern</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Testing a Gatsby application with Cypress on Gitlab CI</title>
      <link>https://haseebmajid.dev/posts/2021-03-22-testing-a-gatsby-application-with-cypress-on-gitlab-ci/</link>
      <pubDate>Mon, 22 Mar 2021 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2021-03-22-testing-a-gatsby-application-with-cypress-on-gitlab-ci/</guid>
      <description>&lt;p&gt;In this blog post, we will go over how we can automatically test a Gatsby site end-to-end (e2e), using Cypress on Gitlab CI.&lt;/p&gt;
&lt;h1 id=&#34;introduction&#34;&gt;Introduction&lt;/h1&gt;
&lt;h2 id=&#34;gatsby&#34;&gt;Gatsby&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://www.gatsbyjs.com&#34;&gt;Gatsby&lt;/a&gt; is a static site generator (SSG) built upon React. It allows us to create &amp;ldquo;blazing&amp;rdquo; fast websites.
In this example, we will use a simple blog starter template available and add a Cypress test.&lt;/p&gt;
&lt;h2 id=&#34;cypress&#34;&gt;Cypress&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Fast, easy and reliable testing for anything that runs in a browser. - Cypress README&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this blog post, we will go over how we can automatically test a Gatsby site end-to-end (e2e), using Cypress on Gitlab CI.</p>
<h1 id="introduction">Introduction</h1>
<h2 id="gatsby">Gatsby</h2>
<p><a href="https://www.gatsbyjs.com">Gatsby</a> is a static site generator (SSG) built upon React. It allows us to create &ldquo;blazing&rdquo; fast websites.
In this example, we will use a simple blog starter template available and add a Cypress test.</p>
<h2 id="cypress">Cypress</h2>
<blockquote>
<p>Fast, easy and reliable testing for anything that runs in a browser. - Cypress README</p>
</blockquote>
<p><a href="http://cypress.io/">Cypress</a> allows us to test a web application, how a real user would use the application.
Cypress will be used to test our Gatsby application, though if it&rsquo;s a site you can test it using Cypress.</p>
<h2 id="gitlab-ci">Gitlab CI</h2>
<p><a href="https://docs.gitlab.com/ee/ci/">Gitlab CI</a> is a continuous integration pipeline that will allow us to run our
tests automatically, such as when we merge code into the master branch.</p>
<h1 id="getting-started">Getting Started</h1>
<h2 id="gatsby-1">Gatsby</h2>
<p>Create a new Gatsby site, using this default Gatsby starter:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">gatsby new gatsby-starter-blog https://github.com/gatsbyjs/gatsby-starter-blog
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> gatsby-starter-blog
</span></span></code></pre></div><h3 id="optional-typescript">(Optional) Typescript</h3>
<p>Adding Typescript to a Gatsby web application.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">yarn add typescript @types/react @types/react-dom @types/node -D
</span></span><span class="line"><span class="cl">yarn add gatsby-plugin-typescript
</span></span></code></pre></div><p>Add the following to your <code>gatsby-config.js</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">plugins</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">resolve</span><span class="o">:</span> <span class="sb">`gatsby-plugin-typescript`</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">options</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">isTSX</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span> <span class="c1">// defaults to false
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nx">jsxPragma</span><span class="o">:</span> <span class="sb">`jsx`</span><span class="p">,</span> <span class="c1">// defaults to &#34;React&#34;
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nx">allExtensions</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span> <span class="c1">// defaults to false
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>      <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Then create a new file <code>tsconfig.json</code> (in the project root, where the <code>gatsby-config.js</code> is).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;compilerOptions&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;module&#34;</span><span class="p">:</span> <span class="s2">&#34;commonjs&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;target&#34;</span><span class="p">:</span> <span class="s2">&#34;esnext&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;jsx&#34;</span><span class="p">:</span> <span class="s2">&#34;preserve&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;lib&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;dom&#34;</span><span class="p">,</span> <span class="s2">&#34;esnext&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;strict&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;noEmit&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;isolatedModules&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;esModuleInterop&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;noUnusedLocals&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;allowJs&#34;</span><span class="p">:</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;exclude&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;node_modules&#34;</span><span class="p">,</span> <span class="s2">&#34;public&#34;</span><span class="p">,</span> <span class="s2">&#34;.cache&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h2 id="cypress-1">Cypress</h2>
<p>Now to finally add Cypress to our application so we can test it. First, install the dependencies.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">yarn add -D cypress cypress-axe axe-core start-server-and-test
</span></span><span class="line"><span class="cl"><span class="c1"># Add types</span>
</span></span><span class="line"><span class="cl">yarn add -D @types/cypress-axe
</span></span></code></pre></div><p>Next, let&rsquo;s create a <code>cypress.json</code> folder in the project root.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;baseUrl&#34;</span><span class="p">:</span> <span class="s2">&#34;http://localhost:8000/&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;integrationFolder&#34;</span><span class="p">:</span> <span class="s2">&#34;cypress/e2e&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Next, let&rsquo;s add some new &ldquo;scripts&rdquo; to the <code>package.json</code> file.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line hl"><span class="cl">  <span class="s2">&#34;scripts&#34;</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;cy:open&#34;</span><span class="p">:</span> <span class="s2">&#34;cypress open&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;cy:run&#34;</span><span class="p">:</span> <span class="s2">&#34;cypress run&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;build&#34;</span><span class="p">:</span> <span class="s2">&#34;gatsby build&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;develop&#34;</span><span class="p">:</span> <span class="s2">&#34;gatsby develop&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;format&#34;</span><span class="p">:</span> <span class="s2">&#34;prettier --write \&#34;**/*.{js,jsx,ts,tsx,json,md}\&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;start&#34;</span><span class="p">:</span> <span class="s2">&#34;npm run develop&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;serve&#34;</span><span class="p">:</span> <span class="s2">&#34;gatsby serve&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;clean&#34;</span><span class="p">:</span> <span class="s2">&#34;gatsby clean&#34;</span><span class="p">,</span>
</span></span><span class="line hl"><span class="cl">    <span class="nt">&#34;test&#34;</span><span class="p">:</span> <span class="s2">&#34;echo \&#34;Write tests! -&gt; https://gatsby.dev/unit-testing\&#34; &amp;&amp; exit 1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;test:e2e&#34;</span><span class="p">:</span> <span class="s2">&#34;start-server-and-test &#39;yarn develop&#39; http://localhost:8000 &#39;yarn cy:open&#39;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;test:e2e:ci&#34;</span><span class="p">:</span> <span class="s2">&#34;start-server-and-test &#39;yarn develop&#39; http://localhost:8000 &#39;yarn cy:run&#39;&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span></code></pre></div><p>These scripts allow us to start Cypress, <code>cy:open</code> opens a GUI to visualise our tests whereas <code>cy:run</code> does it all
in the terminal (the browser runs in headless mode). Where we will run <code>test:e2e:ci</code> in our CI pipeline, here we use
the <code>start-server-and-test</code> command to start our Gatsby server using <code>yarn develop</code>. Then we run <code>cy:run</code> to
start our tests.</p>
<h3 id="structure">Structure</h3>
<p>Create a new folder called <code>cypress</code> which will look something like this.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">.
</span></span><span class="line"><span class="cl">├── e2e
</span></span><span class="line"><span class="cl">│   └── accessibility.test.ts
</span></span><span class="line"><span class="cl">├── fixtures
</span></span><span class="line"><span class="cl">│   └── graphql.json
</span></span><span class="line"><span class="cl">├── plugins
</span></span><span class="line"><span class="cl">│   └── index.js
</span></span><span class="line"><span class="cl">├── support
</span></span><span class="line"><span class="cl">│   ├── commands.js
</span></span><span class="line"><span class="cl">│   ├── index.d.ts
</span></span><span class="line"><span class="cl">│   └── index.js
</span></span><span class="line"><span class="cl">└── tsconfig.json
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir -p cypress/support
</span></span></code></pre></div><p>Create a file at <code>cypress/support/commands.js</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">Cypress</span><span class="p">.</span><span class="nx">Commands</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="sb">`assertRoute`</span><span class="p">,</span> <span class="p">(</span><span class="nx">route</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">cy</span><span class="p">.</span><span class="nx">url</span><span class="p">().</span><span class="nx">should</span><span class="p">(</span><span class="sb">`equal`</span><span class="p">,</span> <span class="sb">`</span><span class="si">${</span><span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">origin</span><span class="si">}${</span><span class="nx">route</span><span class="si">}</span><span class="sb">`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><p>Add some custom types for Cypress <code>index.d.ts</code> if you are using Typescript.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="c1">/// &lt;reference types=&#34;cypress&#34; /&gt;
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="kr">declare</span> <span class="kr">namespace</span> <span class="nx">Cypress</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">interface</span> <span class="nx">Chainable</span><span class="p">&lt;</span><span class="nt">Subject</span><span class="p">&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm">     * Assert the current URL
</span></span></span><span class="line"><span class="cl"><span class="cm">     * @param route
</span></span></span><span class="line"><span class="cl"><span class="cm">     * @example cy.assertRoute(&#39;/page-2&#39;)
</span></span></span><span class="line"><span class="cl"><span class="cm">     */</span>
</span></span><span class="line"><span class="cl">    <span class="nx">assertRoute</span><span class="p">(</span><span class="nx">route</span>: <span class="kt">string</span><span class="p">)</span><span class="o">:</span> <span class="nx">Chainable</span><span class="p">&lt;</span><span class="nt">any</span><span class="p">&gt;;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm">     * Waits for Gatsby to finish the route change, in order to ensure event handlers are properly setup
</span></span></span><span class="line"><span class="cl"><span class="cm">     */</span>
</span></span><span class="line"><span class="cl">    <span class="nx">waitForRouteChange</span><span class="p">()</span><span class="o">:</span> <span class="nx">Chainable</span><span class="p">&lt;</span><span class="nt">any</span><span class="p">&gt;;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Next create the <code>index.js</code> file, which should look something like this.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="s2">&#34;cypress-axe&#34;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="s2">&#34;./commands&#34;</span>
</span></span></code></pre></div><p>Next, let&rsquo;s create a plugin folder <code>mkdir -p cypress/plugins</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="c1">// / &lt;reference types=&#34;cypress&#34; /&gt;
</span></span></span><span class="line"><span class="cl"><span class="c1">// ***********************************************************
</span></span></span><span class="line"><span class="cl"><span class="c1">// This example plugins/index.js can be used to load plugins
</span></span></span><span class="line"><span class="cl"><span class="c1">//
</span></span></span><span class="line"><span class="cl"><span class="c1">// You can change the location of this file or turn off loading
</span></span></span><span class="line"><span class="cl"><span class="c1">// the plugins file with the &#39;pluginsFile&#39; configuration option.
</span></span></span><span class="line"><span class="cl"><span class="c1">//
</span></span></span><span class="line"><span class="cl"><span class="c1">// You can read more here:
</span></span></span><span class="line"><span class="cl"><span class="c1">// https://on.cypress.io/plugins-guide
</span></span></span><span class="line"><span class="cl"><span class="c1">// ***********************************************************
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="c1">// This function is called when a project is opened or re-opened (e.g. due to
</span></span></span><span class="line"><span class="cl"><span class="c1">// the project&#39;s config changing)
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm"> * @type {Cypress.PluginConfig}
</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span>
</span></span><span class="line"><span class="cl"><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">(</span><span class="nx">on</span><span class="p">,</span> <span class="nx">config</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// `on` is used to hook into various events Cypress emits
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="c1">// `config` is the resolved Cypress config
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="k">return</span> <span class="nx">config</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Now finally let&rsquo;s create our tests folder <code>mkdir -p cypress/e2e</code>.</p>
<h3 id="cypress-axe">cypress-axe</h3>
<p>In this blog post we won&rsquo;t go over any complicated Cypress test we will simply use <code>cypress-axe</code> to test
the accessibility of our (a11y) of our website.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="c1">/// &lt;reference types=&#34;../support/index&#34; /&gt;
</span></span></span><span class="line"><span class="cl"><span class="c1">/// &lt;reference types=&#34;cypress&#34; /&gt;
</span></span></span><span class="line"><span class="cl"><span class="c1">/// &lt;reference types=&#34;@types/cypress-axe&#34; /&gt;
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="nx">describe</span><span class="p">(</span><span class="s2">&#34;Component accessibility test&#34;</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">it</span><span class="p">(</span><span class="s2">&#34;Main Page&#34;</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">cy</span><span class="p">.</span><span class="nx">visit</span><span class="p">(</span><span class="s2">&#34;/&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nx">cy</span><span class="p">.</span><span class="nx">wait</span><span class="p">(</span><span class="mi">500</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nx">cy</span><span class="p">.</span><span class="nx">injectAxe</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="nx">cy</span><span class="p">.</span><span class="nx">checkA11y</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">      <span class="nx">include</span><span class="o">:</span> <span class="p">[[</span><span class="s2">&#34;#___gatsby&#34;</span><span class="p">]],</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span></code></pre></div><p>Note the <code>///</code> comments at the top used to add types for cypress. The test above will go to our
home page and test if it has any a11y violations and if so will fail the test.</p>
<p>We can now run our tests locally by running this command:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">yarn run test:e2e
</span></span></code></pre></div><h2 id="gitlab-ci-1">Gitlab CI</h2>
<p>Now how can we automate this, so the tests will run say every time we make changes on the master branch to make
sure we haven&rsquo;t broken any a11y. Create a new <code>.gitlab-ci.yml</code> or add the following job to an existing CI file.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">node:12.14.1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">CYPRESS_CACHE_FOLDER</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;$CI_PROJECT_DIR/cache/Cypress&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">cache</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">${CI_COMMIT_REF_SLUG}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">cache/Cypress</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">node_modules</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">before_script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">yarn install</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">tests</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">cypress/browsers:node12.14.1-chrome83-ff77</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn test:e2e:ci</span><span class="w">
</span></span></span></code></pre></div><p>I won&rsquo;t go into the details of what makes up a Gitlab CI file. At the top of the file, we will cache the <code>node_modules</code>
file so we can share it between the job and the Cypress cache.
The job itself is very simple it uses a <code>cypress/browsers:node12.14.1-chrome83-ff77</code> Docker
image which provides a headless chrome browser that Cypress can leverage to run the tests.
As we won&rsquo;t have access to a GUI in the Gitlab CI runner. The <code>tests</code> job is very simple it runs <code>yarn test:e2e:ci</code> to
run our Cypress tests.</p>
<p>That&rsquo;s it, quite simple to add Cypress tests that run in our CI pipeline.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2021-03-22-testing-a-gatsby-application-with-cypress-on-gitlab-ci/source_code">Example source code</a></li>
<li><a href="http://cypress.io/">Cypress</a></li>
<li><a href="https://gitlab.com/hmajid2301/portfolio-site/-/jobs/1080367107">Example Job</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to create a Golang Web Application using Fizz</title>
      <link>https://haseebmajid.dev/posts/2021-01-19-how-to-create-a-golang-web-application-using-fizz/</link>
      <pubDate>Tue, 19 Jan 2021 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2021-01-19-how-to-create-a-golang-web-application-using-fizz/</guid>
      <description>&lt;h1 id=&#34;background&#34;&gt;Background&lt;/h1&gt;
&lt;p&gt;A bit of background before we start the article. When I develop a Python web service I use the
&lt;a href=&#34;https://github.com/zalando/connexion&#34;&gt;Connexion library created by Zalando&lt;/a&gt;. It&amp;rsquo;s a great library which is built on top of
Flask. It uses an OpenAPI Specification (OAS) file to handle input validation and routing for you. Therefore reducing the boilerplate code you need to write.&lt;/p&gt;
&lt;p&gt;The main advantage of this is that we have a design-first approach to developing our API. We fully define the
OAS then develop the code/web service. This also keeps the OAS up to date, helping to mitigate the issue of the
code/documentation getting out of date. Especially when you share the OAS with other people (clients) to use. The last
thing you want to do is give them an out-of-date file.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h1 id="background">Background</h1>
<p>A bit of background before we start the article. When I develop a Python web service I use the
<a href="https://github.com/zalando/connexion">Connexion library created by Zalando</a>. It&rsquo;s a great library which is built on top of
Flask. It uses an OpenAPI Specification (OAS) file to handle input validation and routing for you. Therefore reducing the boilerplate code you need to write.</p>
<p>The main advantage of this is that we have a design-first approach to developing our API. We fully define the
OAS then develop the code/web service. This also keeps the OAS up to date, helping to mitigate the issue of the
code/documentation getting out of date. Especially when you share the OAS with other people (clients) to use. The last
thing you want to do is give them an out-of-date file.</p>
<p>Anyways short story aside, recently I started learning Golang and developing a simple CRUD web service using Gin.
However, I discovered (at least at the time of writing) there was no equivalent library to Connexion. The closest
library I could find was Fizz.</p>
<h2 id="what-is-fizz">What is Fizz?</h2>
<p>Fizz almost works the opposite way Connexion does. It generates an OAS
file from our code. Now again I prefer the Connexion approach because we just use
the OAS file we created at the beginning of the project. However, this is the next best thing.</p>
<p>What I ended up doing was creating an OAS by hand. Then implementing that OAS using Golang and letting Fizz
auto-generate the &ldquo;new&rdquo; OAS. This &ldquo;new&rdquo; OAS is the one that gets shared with clients and is kept up to date.
In theory the OAS I defined manually can now be deleted as it&rsquo;s not required anymore.</p>
<p>This solves the problem of our code getting out-of-date with the specification.
Fizz also uses other libraries behind the scenes to help us reduce the boilerplate code similiar to
how Connexion works.</p>
<h1 id="web-service">Web Service</h1>
<p>Now onto the real meat and potatoes of this article. We will create three different endpoints:</p>
<ul>
<li>GET /healthcheck: Checks if the application is healthy or not</li>
<li>GET /pet/{name}: Get information about a single pet</li>
<li>PUT /pet/{name}: Update information about a single pet</li>
</ul>
<h2 id="structure">Structure</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"></code></pre></div><p>Our project will follow the structure shown above. We will go (no pun intended 🤷) over what each of the folder &ldquo;do&rdquo;.</p>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">Core Code</summary>
  
  <p>Since this example application is so simple we don&rsquo;t have a <code>core</code> folder but for more complicated
applications you should probably add another folder inline with the <code>server</code> folder. For example, this could include code that interacts with the database.</p>
<p>This helps to de-couple the application&rsquo;s various layers. You could, for example, remove the web service part in the <code>server</code> folder and
turn into a CLI application at a later date. Using the core code you already have.</p>

</details>

<h2 id="dependencies">Dependencies</h2>
<p>The main dependency for this project is <a href="https://github.com/wI2L/fizz">Fizz</a>. Simply run <code>go get github.com/wI2L/fizz</code> to
install it.</p>
<h2 id="internal">internal</h2>
<p>The main logic of our web service will be stored within the <code>internal</code> folder.</p>
<h3 id="server">server</h3>
<p>This folder contains all the logic related to the web service itself. This will include
models (data structure returned to the client) and the controllers, which are functions
that will handle the various requests sent by clients. They act as an &ldquo;interface&rdquo; to our application.</p>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">Fizz Routing</summary>
  
  The Fizz library abstracts away routing partially for us, more on this later.
</details>

<h4 id="models">models</h4>
<p>This folder contains all the data structure and data types that will be received by the application
from the client or sent back to the client from the application. For example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span> <span class="nx">models</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">Pet</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Name</span>  <span class="kt">string</span> <span class="s">`json:&#34;name&#34;`</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Price</span> <span class="kt">int</span>    <span class="s">`json:&#34;price&#34;`</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Breed</span> <span class="kt">string</span> <span class="s">`json:&#34;breed&#34;`</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This will be the object sent back to the client when they request to get a pet. Note the use of struct tags
<code>json:&quot;name&quot;</code>. When the data is unmarshaled from JSON to this struct (again we will see how this done later) the <code>Name</code>
field will look for the <code>name</code> field in the JSON file. Later on we will see why we need to specify struct tags and not
just us being explicit.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span> <span class="nx">models</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">PetParams</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Name</span> <span class="kt">string</span> <span class="s">`query:&#34;name&#34;`</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Note the struct tag in this example is <code>query</code> and not <code>json</code> because it&rsquo;s used as a query parameter.
We also have one final type of model to take a look at:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span> <span class="nx">models</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// PetsInput is the body data and params combined into a single struct.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">type</span> <span class="nx">PetInput</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">PetParams</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Pet</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This model is used when we need to pass both a Pet struct in the body of a request and also a query parameter. Again
we will see exactly how we use this model a bit later.</p>
<h4 id="controllers">controllers</h4>
<p>The controllers folder contains the main web service logic for the application. It contains the one function for every
route/endpoint you have in your application. Let&rsquo;s take a look at the maintenance controller first</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span> <span class="nx">controllers</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;net&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;time&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="s">&#34;github.com/gin-gonic/gin&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;github.com/juju/errors&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="s">&#34;gitlab.com/hmajid2301/articles/example-fizz-project/internal/server/models&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">Healthcheck</span><span class="p">(</span><span class="nx">_</span> <span class="o">*</span><span class="nx">gin</span><span class="p">.</span><span class="nx">Context</span><span class="p">)</span> <span class="p">(</span><span class="o">*</span><span class="nx">models</span><span class="p">.</span><span class="nx">Healthcheck</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">host</span> <span class="o">:=</span> <span class="s">&#34;example.com&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="nx">port</span> <span class="o">:=</span> <span class="s">&#34;80&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="nx">timeout</span> <span class="o">:=</span> <span class="nx">time</span><span class="p">.</span><span class="nf">Duration</span><span class="p">(</span><span class="mi">1</span> <span class="o">*</span> <span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="nx">_</span><span class="p">,</span> <span class="nx">healthy</span> <span class="o">:=</span> <span class="nx">net</span><span class="p">.</span><span class="nf">DialTimeout</span><span class="p">(</span><span class="s">&#34;tcp&#34;</span><span class="p">,</span> <span class="nx">host</span><span class="o">+</span><span class="s">&#34;:&#34;</span><span class="o">+</span><span class="nx">port</span><span class="p">,</span> <span class="nx">timeout</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">healthy</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="o">&amp;</span><span class="nx">models</span><span class="p">.</span><span class="nx">Healthcheck</span><span class="p">{},</span> <span class="nx">errors</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;Healthcheck Failed!&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="o">&amp;</span><span class="nx">models</span><span class="p">.</span><span class="nx">Healthcheck</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">Message</span><span class="p">:</span> <span class="s">&#34;The API is healthy.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="p">},</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>So we have defined a new function, which receives a single argument the gin context (which we don&rsquo;t use, hence the <code>_</code>).
This function returns the health check model. It simply checks if we can connect to
<code>example.com:80</code> (on port 80).</p>
<p>Fizz uses the <a href="https://github.com/loopfz/gadgeto/tree/master/tonic">Tonic library</a> to assign function handlers to our
route.</p>
<blockquote>
<p>Package tonic handles path/query/body parameter binding in a single consolidated input object which allows you to remove all the boilerplate code that retrieves and tests the presence of various parameters. - Tonic README</p>
</blockquote>
<p>We need to specify two return types in the function definition because this function is a handler set using Tonic
Again we will see how we do this in the <code>routes.go</code> file. The first return type is a struct, which will be
returned to the client (marshalled into JSON). In the example above this is the <code>*models.Healthcheck</code>.
The second is an <code>error</code>, again we will see how errors are handled a bit later.</p>
<p>Let&rsquo;s now take a look at the pets controller.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span> <span class="nx">controllers</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;github.com/gin-gonic/gin&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;github.com/juju/errors&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;gitlab.com/hmajid2301/articles/example-fizz-project/internal/server/models&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">GetPet</span><span class="p">(</span><span class="nx">_</span> <span class="o">*</span><span class="nx">gin</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span> <span class="nx">params</span> <span class="o">*</span><span class="nx">models</span><span class="p">.</span><span class="nx">PetParams</span><span class="p">)</span> <span class="p">(</span><span class="nx">models</span><span class="p">.</span><span class="nx">Pet</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">params</span><span class="p">.</span><span class="nx">Name</span> <span class="o">!=</span> <span class="s">&#34;bob&#34;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="nx">models</span><span class="p">.</span><span class="nx">Pet</span><span class="p">{},</span> <span class="nx">errors</span><span class="p">.</span><span class="nf">NotFoundf</span><span class="p">(</span><span class="s">&#34;Pet %s&#34;</span><span class="p">,</span> <span class="nx">params</span><span class="p">.</span><span class="nx">Name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="nx">models</span><span class="p">.</span><span class="nx">Pet</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">Name</span><span class="p">:</span>  <span class="s">&#34;bob&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nx">Price</span><span class="p">:</span> <span class="mi">100</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nx">Breed</span><span class="p">:</span> <span class="s">&#34;bengal&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="p">},</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">UpdatePet</span><span class="p">(</span><span class="nx">_</span> <span class="o">*</span><span class="nx">gin</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span> <span class="nx">input</span> <span class="o">*</span><span class="nx">models</span><span class="p">.</span><span class="nx">PetInput</span><span class="p">)</span> <span class="p">(</span><span class="nx">models</span><span class="p">.</span><span class="nx">Pet</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">input</span><span class="p">.</span><span class="nx">PetParams</span><span class="p">.</span><span class="nx">Name</span> <span class="o">!=</span> <span class="s">&#34;bob&#34;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="nx">models</span><span class="p">.</span><span class="nx">Pet</span><span class="p">{},</span> <span class="nx">errors</span><span class="p">.</span><span class="nf">NotFoundf</span><span class="p">(</span><span class="s">&#34;Pet %s&#34;</span><span class="p">,</span> <span class="nx">input</span><span class="p">.</span><span class="nx">PetParams</span><span class="p">.</span><span class="nx">Name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="nx">input</span><span class="p">.</span><span class="nx">Pet</span><span class="p">,</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The first function:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">GetPet</span><span class="p">(</span><span class="nx">_</span> <span class="o">*</span><span class="nx">gin</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span> <span class="nx">params</span> <span class="o">*</span><span class="nx">models</span><span class="p">.</span><span class="nx">PetParams</span><span class="p">)</span> <span class="p">(</span><span class="nx">models</span><span class="p">.</span><span class="nx">Pet</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">params</span><span class="p">.</span><span class="nx">Name</span> <span class="o">!=</span> <span class="s">&#34;bob&#34;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="nx">models</span><span class="p">.</span><span class="nx">Pet</span><span class="p">{},</span> <span class="nx">errors</span><span class="p">.</span><span class="nf">NotFoundf</span><span class="p">(</span><span class="s">&#34;Pet %s&#34;</span><span class="p">,</span> <span class="nx">params</span><span class="p">.</span><span class="nx">Name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="nx">models</span><span class="p">.</span><span class="nx">Pet</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">Name</span><span class="p">:</span>  <span class="s">&#34;bob&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nx">Price</span><span class="p">:</span> <span class="mi">100</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nx">Breed</span><span class="p">:</span> <span class="s">&#34;bengal&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="p">},</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><details
  class="notice info"
  open="true"
>
    <summary class="notice-title">JuJu Errors</summary>
  
  For throwing errors in this application we used <a href="https://github.com/juju/errors">juju&rsquo;s error library</a>.
</details>

<p>The main difference in this function is we pass in an extra parameter which is the query parameter <code>{name}</code>. The logic of this function is not very smart because
it expects the name of the pet to be <code>bob</code> in order to send a successful respone back to the client. Of course in
reality you would look in your data store for information about the pet.</p>
<p>The second function looks like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">UpdatePet</span><span class="p">(</span><span class="nx">_</span> <span class="o">*</span><span class="nx">gin</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span> <span class="nx">input</span> <span class="o">*</span><span class="nx">models</span><span class="p">.</span><span class="nx">PetInput</span><span class="p">)</span> <span class="p">(</span><span class="nx">models</span><span class="p">.</span><span class="nx">Pet</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">input</span><span class="p">.</span><span class="nx">PetParams</span><span class="p">.</span><span class="nx">Name</span> <span class="o">!=</span> <span class="s">&#34;bob&#34;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="nx">models</span><span class="p">.</span><span class="nx">Pet</span><span class="p">{},</span> <span class="nx">errors</span><span class="p">.</span><span class="nf">NotFoundf</span><span class="p">(</span><span class="s">&#34;Pet %s&#34;</span><span class="p">,</span> <span class="nx">input</span><span class="p">.</span><span class="nx">PetParams</span><span class="p">.</span><span class="nx">Name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="nx">input</span><span class="p">.</span><span class="nx">Pet</span><span class="p">,</span> <span class="kc">nil</span>
</span></span></code></pre></div><p>Again this is slightly different because the client sends both a HTTP body and a path query parameter. So the
<code>input</code> argument is a combination of two structs:</p>
<p>We can access the query parameter like so <code>input.PetParams.Name</code> and the pet&rsquo;s data like <code>input.Pet</code>. Note how we
use the name of the struct after <code>input</code>. This is how we can combine the body, query parameters and also the query
string into a single struct. The struct tags are really important as they let Tonic know what type of data that field is
i.e. <code>json</code> or <code>query</code> etc.</p>
<p>Again we can ignore the logic of the function itself. It&rsquo;s not supposed to be very complicated. Just more of an
example of how we can use Fizz, with more complicated HTTP requests.</p>
<h4 id="routesgo">routes.go</h4>
<p>This file is where we link the routes to their specific handler functions (using Tonic). This is also where we
provide most of the data that will be used to populate the OAS file.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">NewRouter</span><span class="p">()</span> <span class="p">(</span><span class="o">*</span><span class="nx">fizz</span><span class="p">.</span><span class="nx">Fizz</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">engine</span> <span class="o">:=</span> <span class="nx">gin</span><span class="p">.</span><span class="nf">New</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nx">engine</span><span class="p">.</span><span class="nf">Use</span><span class="p">(</span><span class="nx">cors</span><span class="p">.</span><span class="nf">Default</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nx">fizzApp</span> <span class="o">:=</span> <span class="nx">fizz</span><span class="p">.</span><span class="nf">NewFromEngine</span><span class="p">(</span><span class="nx">engine</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nx">infos</span> <span class="o">:=</span> <span class="o">&amp;</span><span class="nx">openapi</span><span class="p">.</span><span class="nx">Info</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">Title</span><span class="p">:</span>       <span class="s">&#34;Example API&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nx">Description</span><span class="p">:</span> <span class="s">&#34;The API definition for the Example API.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nx">Version</span><span class="p">:</span>     <span class="s">&#34;1.0.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nx">fizzApp</span><span class="p">.</span><span class="nf">GET</span><span class="p">(</span><span class="s">&#34;/openapi.json&#34;</span><span class="p">,</span> <span class="kc">nil</span><span class="p">,</span> <span class="nx">fizzApp</span><span class="p">.</span><span class="nf">OpenAPI</span><span class="p">(</span><span class="nx">infos</span><span class="p">,</span> <span class="s">&#34;json&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nx">group</span> <span class="o">:=</span> <span class="nx">fizzApp</span><span class="p">.</span><span class="nf">Group</span><span class="p">(</span><span class="s">&#34;&#34;</span><span class="p">,</span> <span class="s">&#34;maintenance&#34;</span><span class="p">,</span> <span class="s">&#34;Related to managing the maintenance of the API.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="nx">group</span><span class="p">.</span><span class="nf">GET</span><span class="p">(</span><span class="s">&#34;/healthcheck&#34;</span><span class="p">,</span> <span class="p">[]</span><span class="nx">fizz</span><span class="p">.</span><span class="nx">OperationOption</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">fizz</span><span class="p">.</span><span class="nf">Summary</span><span class="p">(</span><span class="s">&#34;Checks API is healthy.&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">		<span class="nx">fizz</span><span class="p">.</span><span class="nf">Response</span><span class="p">(</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprint</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusInternalServerError</span><span class="p">),</span> <span class="s">&#34;Server Error&#34;</span><span class="p">,</span> <span class="nx">models</span><span class="p">.</span><span class="nx">APIError</span><span class="p">{},</span> <span class="kc">nil</span><span class="p">,</span> <span class="kc">nil</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">	<span class="p">},</span> <span class="nx">tonic</span><span class="p">.</span><span class="nf">Handler</span><span class="p">(</span><span class="nx">controllers</span><span class="p">.</span><span class="nx">Healthcheck</span><span class="p">,</span> <span class="nx">http</span><span class="p">.</span><span class="nx">StatusOK</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nx">group</span><span class="p">.</span><span class="nf">GET</span><span class="p">(</span><span class="s">&#34;/pets:name&#34;</span><span class="p">,</span> <span class="p">[]</span><span class="nx">fizz</span><span class="p">.</span><span class="nx">OperationOption</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">fizz</span><span class="p">.</span><span class="nf">Summary</span><span class="p">(</span><span class="s">&#34;Get a pet by name.&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">		<span class="nx">fizz</span><span class="p">.</span><span class="nf">Response</span><span class="p">(</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprint</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusInternalServerError</span><span class="p">),</span> <span class="s">&#34;Server Error&#34;</span><span class="p">,</span> <span class="nx">models</span><span class="p">.</span><span class="nx">APIError</span><span class="p">{},</span> <span class="kc">nil</span><span class="p">,</span> <span class="kc">nil</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">		<span class="nx">fizz</span><span class="p">.</span><span class="nf">Response</span><span class="p">(</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprint</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusNotFound</span><span class="p">),</span> <span class="s">&#34;Pet Not Found&#34;</span><span class="p">,</span> <span class="nx">models</span><span class="p">.</span><span class="nx">APIError</span><span class="p">{},</span> <span class="kc">nil</span><span class="p">,</span> <span class="kc">nil</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">	<span class="p">},</span> <span class="nx">tonic</span><span class="p">.</span><span class="nf">Handler</span><span class="p">(</span><span class="nx">controllers</span><span class="p">.</span><span class="nx">GetPet</span><span class="p">,</span> <span class="nx">http</span><span class="p">.</span><span class="nx">StatusOK</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nx">group</span><span class="p">.</span><span class="nf">PUT</span><span class="p">(</span><span class="s">&#34;/pets:name&#34;</span><span class="p">,</span> <span class="p">[]</span><span class="nx">fizz</span><span class="p">.</span><span class="nx">OperationOption</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">fizz</span><span class="p">.</span><span class="nf">Summary</span><span class="p">(</span><span class="s">&#34;Update a pet.&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">		<span class="nx">fizz</span><span class="p">.</span><span class="nf">Response</span><span class="p">(</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprint</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusInternalServerError</span><span class="p">),</span> <span class="s">&#34;Server Error&#34;</span><span class="p">,</span> <span class="nx">models</span><span class="p">.</span><span class="nx">APIError</span><span class="p">{},</span> <span class="kc">nil</span><span class="p">,</span> <span class="kc">nil</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">	<span class="p">},</span> <span class="nx">tonic</span><span class="p">.</span><span class="nf">Handler</span><span class="p">(</span><span class="nx">controllers</span><span class="p">.</span><span class="nx">UpdatePet</span><span class="p">,</span> <span class="nx">http</span><span class="p">.</span><span class="nx">StatusOK</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="nx">fizzApp</span><span class="p">.</span><span class="nf">Errors</span><span class="p">())</span> <span class="o">!=</span> <span class="mi">0</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="kc">nil</span><span class="p">,</span> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;fizz errors: %v&#34;</span><span class="p">,</span> <span class="nx">fizzApp</span><span class="p">.</span><span class="nf">Errors</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="nx">tonic</span><span class="p">.</span><span class="nf">SetErrorHook</span><span class="p">(</span><span class="nx">errHook</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="nx">fizzApp</span><span class="p">,</span> <span class="kc">nil</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Let&rsquo;s break this function down:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl">	<span class="nx">engine</span> <span class="o">:=</span> <span class="nx">gin</span><span class="p">.</span><span class="nf">New</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nx">engine</span><span class="p">.</span><span class="nf">Use</span><span class="p">(</span><span class="nx">cors</span><span class="p">.</span><span class="nf">Default</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nx">fizzApp</span> <span class="o">:=</span> <span class="nx">fizz</span><span class="p">.</span><span class="nf">NewFromEngine</span><span class="p">(</span><span class="nx">engine</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nx">infos</span> <span class="o">:=</span> <span class="o">&amp;</span><span class="nx">openapi</span><span class="p">.</span><span class="nx">Info</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">Title</span><span class="p">:</span>       <span class="s">&#34;Example API&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nx">Description</span><span class="p">:</span> <span class="s">&#34;The API definition for the Example API.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nx">Version</span><span class="p">:</span>     <span class="s">&#34;1.0.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nx">fizzApp</span><span class="p">.</span><span class="nf">GET</span><span class="p">(</span><span class="s">&#34;/openapi.json&#34;</span><span class="p">,</span> <span class="kc">nil</span><span class="p">,</span> <span class="nx">fizzApp</span><span class="p">.</span><span class="nf">OpenAPI</span><span class="p">(</span><span class="nx">infos</span><span class="p">,</span> <span class="s">&#34;json&#34;</span><span class="p">)</span>
</span></span></code></pre></div><p>First, we create the Gin engine and share this with a new Fizz engine. Fizz just uses Gin behind the scenes.
Then we create an info struct, which stores the metadata for the generated OAS file. Then we add a new route
<code>/openapi.json</code>, which will serve the OAS file.</p>
<p>Note we could change the path if we wanted and serve
a YAML file as well <code>fizzApp.GET(&quot;/openapi&quot;, nil, fizzApp.OpenAPI(infos, &quot;yaml&quot;))</code>. Here we removed the
extension and changed the generated file so that we will serve the client a YAML file.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl">	<span class="nx">group</span> <span class="o">:=</span> <span class="nx">fizzApp</span><span class="p">.</span><span class="nf">Group</span><span class="p">(</span><span class="s">&#34;&#34;</span><span class="p">,</span> <span class="s">&#34;endpoints&#34;</span><span class="p">,</span> <span class="s">&#34;All of the endpoints.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="nx">group</span><span class="p">.</span><span class="nf">GET</span><span class="p">(</span><span class="s">&#34;/healthcheck&#34;</span><span class="p">,</span> <span class="p">[]</span><span class="nx">fizz</span><span class="p">.</span><span class="nx">OperationOption</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">fizz</span><span class="p">.</span><span class="nf">Summary</span><span class="p">(</span><span class="s">&#34;Checks API is healthy.&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">		<span class="nx">fizz</span><span class="p">.</span><span class="nf">Response</span><span class="p">(</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprint</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusInternalServerError</span><span class="p">),</span> <span class="s">&#34;Server Error&#34;</span><span class="p">,</span> <span class="nx">models</span><span class="p">.</span><span class="nx">APIError</span><span class="p">{},</span> <span class="kc">nil</span><span class="p">,</span> <span class="kc">nil</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">	<span class="p">},</span> <span class="nx">tonic</span><span class="p">.</span><span class="nf">Handler</span><span class="p">(</span><span class="nx">controllers</span><span class="p">.</span><span class="nx">Healthcheck</span><span class="p">,</span> <span class="nx">http</span><span class="p">.</span><span class="nx">StatusOK</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nx">group</span><span class="p">.</span><span class="nf">GET</span><span class="p">(</span><span class="s">&#34;/pets:name&#34;</span><span class="p">,</span> <span class="p">[]</span><span class="nx">fizz</span><span class="p">.</span><span class="nx">OperationOption</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">fizz</span><span class="p">.</span><span class="nf">Summary</span><span class="p">(</span><span class="s">&#34;Get a pet by name.&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">		<span class="nx">fizz</span><span class="p">.</span><span class="nf">Response</span><span class="p">(</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprint</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusInternalServerError</span><span class="p">),</span> <span class="s">&#34;Server Error&#34;</span><span class="p">,</span> <span class="nx">models</span><span class="p">.</span><span class="nx">APIError</span><span class="p">{},</span> <span class="kc">nil</span><span class="p">,</span> <span class="kc">nil</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">		<span class="nx">fizz</span><span class="p">.</span><span class="nf">Response</span><span class="p">(</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprint</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusNotFound</span><span class="p">),</span> <span class="s">&#34;Pet Not Found&#34;</span><span class="p">,</span> <span class="nx">models</span><span class="p">.</span><span class="nx">APIError</span><span class="p">{},</span> <span class="kc">nil</span><span class="p">,</span> <span class="kc">nil</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">	<span class="p">},</span> <span class="nx">tonic</span><span class="p">.</span><span class="nf">Handler</span><span class="p">(</span><span class="nx">controllers</span><span class="p">.</span><span class="nx">GetPet</span><span class="p">,</span> <span class="nx">http</span><span class="p">.</span><span class="nx">StatusOK</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nx">group</span><span class="p">.</span><span class="nf">PUT</span><span class="p">(</span><span class="s">&#34;/pets:name&#34;</span><span class="p">,</span> <span class="p">[]</span><span class="nx">fizz</span><span class="p">.</span><span class="nx">OperationOption</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">fizz</span><span class="p">.</span><span class="nf">Summary</span><span class="p">(</span><span class="s">&#34;Update a pet.&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">		<span class="nx">fizz</span><span class="p">.</span><span class="nf">Response</span><span class="p">(</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprint</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusInternalServerError</span><span class="p">),</span> <span class="s">&#34;Server Error&#34;</span><span class="p">,</span> <span class="nx">models</span><span class="p">.</span><span class="nx">APIError</span><span class="p">{},</span> <span class="kc">nil</span><span class="p">,</span> <span class="kc">nil</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">	<span class="p">},</span> <span class="nx">tonic</span><span class="p">.</span><span class="nf">Handler</span><span class="p">(</span><span class="nx">controllers</span><span class="p">.</span><span class="nx">UpdatePet</span><span class="p">,</span> <span class="nx">http</span><span class="p">.</span><span class="nx">StatusOK</span><span class="p">))</span>
</span></span></code></pre></div><p>Next, let&rsquo;s get to the part of the function where we define our routes. First, we create a group, this will group the
routes within the OAS (such as the tag).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl">	<span class="nx">group</span><span class="p">.</span><span class="nf">GET</span><span class="p">(</span><span class="s">&#34;/pets:name&#34;</span><span class="p">,</span> <span class="p">[]</span><span class="nx">fizz</span><span class="p">.</span><span class="nx">OperationOption</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">fizz</span><span class="p">.</span><span class="nf">Summary</span><span class="p">(</span><span class="s">&#34;Get a pet by name.&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">		<span class="nx">fizz</span><span class="p">.</span><span class="nf">Response</span><span class="p">(</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprint</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusInternalServerError</span><span class="p">),</span> <span class="s">&#34;Server Error&#34;</span><span class="p">,</span> <span class="nx">models</span><span class="p">.</span><span class="nx">APIError</span><span class="p">{},</span> <span class="kc">nil</span><span class="p">,</span> <span class="kc">nil</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">		<span class="nx">fizz</span><span class="p">.</span><span class="nf">Response</span><span class="p">(</span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Sprint</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusNotFound</span><span class="p">),</span> <span class="s">&#34;Pet Not Found&#34;</span><span class="p">,</span> <span class="nx">models</span><span class="p">.</span><span class="nx">APIError</span><span class="p">{},</span> <span class="kc">nil</span><span class="p">,</span> <span class="kc">nil</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">	<span class="p">},</span> <span class="nx">tonic</span><span class="p">.</span><span class="nf">Handler</span><span class="p">(</span><span class="nx">controllers</span><span class="p">.</span><span class="nx">GetPet</span><span class="p">,</span> <span class="nx">http</span><span class="p">.</span><span class="nx">StatusOK</span><span class="p">))</span>
</span></span></code></pre></div><p>Next, let&rsquo;s take a look at how we define a new route. We add it to our existing group, then we give it some
information to add to the OAS such as summary. What responses we can get here I have defined the possible
errors. Note that because I prefer not to use magic numbers I have used the <code>http</code> package constants instead
of using numbers i.e. 404 -&gt; <code>http.StatusNotFound</code>. And of course the most important bit, the Tonic handler
where we tell this route what function to call when a client sends a request to this route. In this case, we
choose the <code>GetPet</code> function we mentioned earlier and on a successful response we return a <code>200</code> status code i.e.
<code>http.StatusOK</code>.</p>
<p>You can define whichever status code you want here such as an <code>http.StatusCreated</code> or <code>http.NoContent</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl">	<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="nx">fizzApp</span><span class="p">.</span><span class="nf">Errors</span><span class="p">())</span> <span class="o">!=</span> <span class="mi">0</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="kc">nil</span><span class="p">,</span> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;fizz errors: %v&#34;</span><span class="p">,</span> <span class="nx">fizzApp</span><span class="p">.</span><span class="nf">Errors</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="nx">tonic</span><span class="p">.</span><span class="nf">SetErrorHook</span><span class="p">(</span><span class="nx">errHook</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="nx">fizzApp</span><span class="p">,</span> <span class="kc">nil</span>
</span></span></code></pre></div><p>The final part of the function checks if Fizz returned any errors and sets up the Tonic error hook. What to do if
any of the Tonic function handler return an error. As we saw earlier with some of the functions
returning errors.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">errHook</span><span class="p">(</span><span class="nx">_</span> <span class="o">*</span><span class="nx">gin</span><span class="p">.</span><span class="nx">Context</span><span class="p">,</span> <span class="nx">e</span> <span class="kt">error</span><span class="p">)</span> <span class="p">(</span><span class="kt">int</span><span class="p">,</span> <span class="kd">interface</span><span class="p">{})</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">code</span><span class="p">,</span> <span class="nx">msg</span> <span class="o">:=</span> <span class="nx">http</span><span class="p">.</span><span class="nx">StatusInternalServerError</span><span class="p">,</span> <span class="nx">http</span><span class="p">.</span><span class="nf">StatusText</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusInternalServerError</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">ok</span> <span class="o">:=</span> <span class="nx">e</span><span class="p">.(</span><span class="nx">tonic</span><span class="p">.</span><span class="nx">BindError</span><span class="p">);</span> <span class="nx">ok</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">code</span><span class="p">,</span> <span class="nx">msg</span> <span class="p">=</span> <span class="nx">http</span><span class="p">.</span><span class="nx">StatusBadRequest</span><span class="p">,</span> <span class="nx">e</span><span class="p">.</span><span class="nf">Error</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">switch</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">case</span> <span class="nx">errors</span><span class="p">.</span><span class="nf">IsBadRequest</span><span class="p">(</span><span class="nx">e</span><span class="p">),</span> <span class="nx">errors</span><span class="p">.</span><span class="nf">IsNotValid</span><span class="p">(</span><span class="nx">e</span><span class="p">),</span> <span class="nx">errors</span><span class="p">.</span><span class="nf">IsNotSupported</span><span class="p">(</span><span class="nx">e</span><span class="p">),</span> <span class="nx">errors</span><span class="p">.</span><span class="nf">IsNotProvisioned</span><span class="p">(</span><span class="nx">e</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">			<span class="nx">code</span><span class="p">,</span> <span class="nx">msg</span> <span class="p">=</span> <span class="nx">http</span><span class="p">.</span><span class="nx">StatusBadRequest</span><span class="p">,</span> <span class="nx">e</span><span class="p">.</span><span class="nf">Error</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">		<span class="k">case</span> <span class="nx">errors</span><span class="p">.</span><span class="nf">IsForbidden</span><span class="p">(</span><span class="nx">e</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">			<span class="nx">code</span><span class="p">,</span> <span class="nx">msg</span> <span class="p">=</span> <span class="nx">http</span><span class="p">.</span><span class="nx">StatusForbidden</span><span class="p">,</span> <span class="nx">e</span><span class="p">.</span><span class="nf">Error</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">		<span class="k">case</span> <span class="nx">errors</span><span class="p">.</span><span class="nf">IsMethodNotAllowed</span><span class="p">(</span><span class="nx">e</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">			<span class="nx">code</span><span class="p">,</span> <span class="nx">msg</span> <span class="p">=</span> <span class="nx">http</span><span class="p">.</span><span class="nx">StatusMethodNotAllowed</span><span class="p">,</span> <span class="nx">e</span><span class="p">.</span><span class="nf">Error</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">		<span class="k">case</span> <span class="nx">errors</span><span class="p">.</span><span class="nf">IsNotFound</span><span class="p">(</span><span class="nx">e</span><span class="p">),</span> <span class="nx">errors</span><span class="p">.</span><span class="nf">IsUserNotFound</span><span class="p">(</span><span class="nx">e</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">			<span class="nx">code</span><span class="p">,</span> <span class="nx">msg</span> <span class="p">=</span> <span class="nx">http</span><span class="p">.</span><span class="nx">StatusNotFound</span><span class="p">,</span> <span class="nx">e</span><span class="p">.</span><span class="nf">Error</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">		<span class="k">case</span> <span class="nx">errors</span><span class="p">.</span><span class="nf">IsUnauthorized</span><span class="p">(</span><span class="nx">e</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">			<span class="nx">code</span><span class="p">,</span> <span class="nx">msg</span> <span class="p">=</span> <span class="nx">http</span><span class="p">.</span><span class="nx">StatusUnauthorized</span><span class="p">,</span> <span class="nx">e</span><span class="p">.</span><span class="nf">Error</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">		<span class="k">case</span> <span class="nx">errors</span><span class="p">.</span><span class="nf">IsAlreadyExists</span><span class="p">(</span><span class="nx">e</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">			<span class="nx">code</span><span class="p">,</span> <span class="nx">msg</span> <span class="p">=</span> <span class="nx">http</span><span class="p">.</span><span class="nx">StatusConflict</span><span class="p">,</span> <span class="nx">e</span><span class="p">.</span><span class="nf">Error</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">		<span class="k">case</span> <span class="nx">errors</span><span class="p">.</span><span class="nf">IsNotImplemented</span><span class="p">(</span><span class="nx">e</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">			<span class="nx">code</span><span class="p">,</span> <span class="nx">msg</span> <span class="p">=</span> <span class="nx">http</span><span class="p">.</span><span class="nx">StatusNotImplemented</span><span class="p">,</span> <span class="nx">e</span><span class="p">.</span><span class="nf">Error</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="nx">err</span> <span class="o">:=</span> <span class="nx">models</span><span class="p">.</span><span class="nx">APIError</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">Message</span><span class="p">:</span> <span class="nx">msg</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="nx">code</span><span class="p">,</span> <span class="nx">err</span>
</span></span></code></pre></div><p>This function receives an error since we are using juju error in our controller functions. We can then use the <code>isX</code>
the function provided by the library to check what kind of error we received. Using a switch statement we then determine
what type of HTTP status code to return to the client depending on the error thrown by the function. For example an <code>NotFoundError</code> means we return <code>http.StatusNotFound</code> (404).</p>
<p>Ok, that&rsquo;s the main part of our application so how do we start our web service?</p>
<h2 id="cmd">cmd</h2>
<p>In our cmd folder, we have the <code>main.go</code> file.</p>
<h3 id="maingo">main.go</h3>
<p>The <code>main.go</code> file, as is good practice in Golang, is used to start our application.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span> <span class="nx">main</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;log&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="s">&#34;net/http&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="s">&#34;gitlab.com/hmajid2301/articles/example-fizz-project/internal/server&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">router</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">server</span><span class="p">.</span><span class="nf">NewRouter</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">log</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="nx">srv</span> <span class="o">:=</span> <span class="o">&amp;</span><span class="nx">http</span><span class="p">.</span><span class="nx">Server</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">Addr</span><span class="p">:</span>    <span class="s">&#34;:8080&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		<span class="nx">Handler</span><span class="p">:</span> <span class="nx">router</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="nx">srv</span><span class="p">.</span><span class="nf">ListenAndServe</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>In the root folder of our application run this command <code>go run cmd/example-fizz-project/main.go</code>. Then you should see something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">go run cmd/example-fizz-project/main.go
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">[</span>GIN-debug<span class="o">]</span> <span class="o">[</span>WARNING<span class="o">]</span> Running in <span class="s2">&#34;debug&#34;</span> mode. Switch to <span class="s2">&#34;release&#34;</span> mode in production.
</span></span><span class="line"><span class="cl"> - using env:   <span class="nb">export</span> <span class="nv">GIN_MODE</span><span class="o">=</span>release
</span></span><span class="line"><span class="cl"> - using code:  gin.SetMode<span class="o">(</span>gin.ReleaseMode<span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">[</span>GIN-debug<span class="o">]</span> GET    /openapi.json             --&gt; github.com/wI2L/fizz.<span class="o">(</span>*Fizz<span class="o">)</span>.OpenAPI.func1 <span class="o">(</span><span class="m">2</span> handlers<span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span>GIN-debug<span class="o">]</span> GET    /healthcheck              --&gt; github.com/wI2L/fizz.<span class="o">(</span>*RouterGroup<span class="o">)</span>.Handle.func1 <span class="o">(</span><span class="m">2</span> handlers<span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span>GIN-debug<span class="o">]</span> GET    /pets:name                --&gt; github.com/wI2L/fizz.<span class="o">(</span>*RouterGroup<span class="o">)</span>.Handle.func1 <span class="o">(</span><span class="m">2</span> handlers<span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="o">[</span>GIN-debug<span class="o">]</span> PUT    /pets:name                --&gt; github.com/wI2L/fizz.<span class="o">(</span>*RouterGroup<span class="o">)</span>.Handle.func1 <span class="o">(</span><span class="m">2</span> handlers<span class="o">)</span>
</span></span></code></pre></div><p>And voila you now have a working web service you have created using Fizz. That&rsquo;s it we have now built a web application with Fizz and Golang.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2021-01-19-how-to-create-a-golang-web-application-using-fizz/source_code">Example source code</a></li>
<li><a href="https://github.com/wI2L/fizz/">Fizz</a></li>
<li><a href="https://github.com/loopfz/gadgeto/tree/master/tonic">Tonic</a></li>
<li><a href="https://github.com/juju/errors">Juju&rsquo;s Errors</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Golang &amp; MongoDB with &#34;Polymorphism&#34; and BSON Unmarshal</title>
      <link>https://haseebmajid.dev/posts/2020-12-20-golang-mongodb-with-polymorphism-and-bson-unmarshal/</link>
      <pubDate>Sun, 20 Dec 2020 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2020-12-20-golang-mongodb-with-polymorphism-and-bson-unmarshal/</guid>
      <description>&lt;p&gt;Recently I&amp;rsquo;ve been working on a new personal project called Banter Bus, a browser-based multiplayer game.
I&amp;rsquo;ve been working on a REST API to add new questions to the game. The API is built in Golang and uses
MongoDB as the database. Since Golang is a strongly typed language, we will need to specify the structure of
the data we expect from the database. This can get tricky if the data varies, such as one field changing.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Recently I&rsquo;ve been working on a new personal project called Banter Bus, a browser-based multiplayer game.
I&rsquo;ve been working on a REST API to add new questions to the game. The API is built in Golang and uses
MongoDB as the database. Since Golang is a strongly typed language, we will need to specify the structure of
the data we expect from the database. This can get tricky if the data varies, such as one field changing.</p>
<p>One issue I encountered was each game type has to have its questions. These questions will be asked to the
users playing the game and are stored differently in the database. This is because each game type has different
rules and therefore needs a different structure. This means when we unmarshal the data in Golang,
we need to specify the structure of these questions. In this article, I will explain how you can create
your own unmarshal function. This will allow you to customise the struct that will hold this data (in Golang)
returned from MongoDB.</p>
<h2 id="collection">Collection</h2>
<p>Imagine the data stored in MongoDB looks something like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">[</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;game_name&#34;</span><span class="p">:</span> <span class="s2">&#34;fibbing_it&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;questions&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;opinion&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;horse_group&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nt">&#34;questions&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;What do you think about horses?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;What do you think about camels?&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="p">],</span>
</span></span><span class="line"><span class="cl">          <span class="nt">&#34;answers&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;lame&#34;</span><span class="p">,</span> <span class="s2">&#34;tasty&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;free_form&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;bike_group&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;Favourite bike colour?&#34;</span><span class="p">,</span> <span class="s2">&#34;A funny question?&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;likely&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;to eat ice-cream from the tub&#34;</span><span class="p">,</span> <span class="s2">&#34;to get arrested&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;game_name&#34;</span><span class="p">:</span> <span class="s2">&#34;quibly&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;questions&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;pair&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;What do you think about horses?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;What do you think about camels?&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">],</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;answers&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;Favourite bike colour?&#34;</span><span class="p">,</span> <span class="s2">&#34;A funny question?&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;game_name&#34;</span><span class="p">:</span> <span class="s2">&#34;drawlosseum&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;questions&#34;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&#34;drawings&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;horses&#34;</span><span class="p">,</span> <span class="s2">&#34;camels&#34;</span><span class="p">]</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span></code></pre></div><p>Here you can see each game type has a different structure, due to the different rules each game type
will have.</p>
<h2 id="unmarshal">Unmarshal</h2>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">BSON</summary>
  
  Binary JSON the format used by MongoDB readme more about it <a href="https://www.mongodb.com/json-and-bson">here</a>
</details>

<p>To do this we need to create a custom BSON unmarshal function. This will work very similarly to JSON unmarshaling.
When we try to get data from MongoDB, doing something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">collection</span> <span class="o">:=</span> <span class="nx">_database</span><span class="p">.</span><span class="nf">Collection</span><span class="p">(</span><span class="s">&#34;games&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">err</span> <span class="o">:=</span> <span class="nx">collection</span><span class="p">.</span><span class="nf">FindOne</span><span class="p">(</span><span class="nx">_ctx</span><span class="p">,</span> <span class="nx">bson</span><span class="p">.</span><span class="nx">M</span><span class="p">{</span><span class="s">&#34;game_name&#34;</span><span class="p">:</span> <span class="s">&#34;quibly&#34;</span><span class="p">}).</span><span class="nf">Decode</span><span class="p">(</span><span class="kd">interface</span><span class="p">{}{})</span>
</span></span></code></pre></div><p>When decoding the object into a struct, MongoDB checks that the (struct) type implements the <code>Umarshaler</code> interface.
It implements this interface if it implements the <code>UnmarshalBSONValue(t bsontype.Type, data []byte) error</code> function.
If the struct type does implement this function, it will use this function instead of the default <code>UnmarshalBSONValue()</code>
function.</p>
<h3 id="example">Example</h3>
<p>Let&rsquo;s take a look at an example, define the following struct.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">QuestionSet</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">GameName</span>  <span class="kt">string</span>      <span class="s">`bson:&#34;game_name&#34;`</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Questions</span> <span class="kd">interface</span><span class="p">{}</span> <span class="s">`bson:&#34;questions&#34;`</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Where the <code>Questions</code> field is the one that can vary between the different game types. Now let&rsquo;s define
the structure of the different game type. As you can see each of the game types will have different
rounds and ask different types of questions.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">DrawlosseumQuestionsPool</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Drawings</span> <span class="p">[]</span><span class="kt">string</span> <span class="s">`bson:&#34;drawings,omitempty&#34;`</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">QuiblyQuestionsPool</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Pair</span>    <span class="p">[]</span><span class="kt">string</span> <span class="s">`bson:&#34;pair,omitempty&#34;`</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Answers</span> <span class="p">[]</span><span class="kt">string</span> <span class="s">`bson:&#34;answers,omitempty&#34;`</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Group</span>   <span class="p">[]</span><span class="kt">string</span> <span class="s">`bson:&#34;group,omitempty&#34;`</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">FibbingItQuestionsPool</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Opinion</span>  <span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">][]</span><span class="kt">string</span> <span class="s">`bson:&#34;opinion,omitempty&#34;`</span>
</span></span><span class="line"><span class="cl">	<span class="nx">FreeForm</span> <span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">][]</span><span class="kt">string</span>            <span class="s">`bson:&#34;free_form,omitempty&#34;`</span>
</span></span><span class="line"><span class="cl">	<span class="nx">Likely</span>   <span class="p">[]</span><span class="kt">string</span>                       <span class="s">`bson:&#34;likely,omitempty&#34;`</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>To get the <code>QuestionSet</code> struct to implement the <code>Unmarshaler</code> interface we need to do something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span> <span class="p">(</span><span class="nx">questionSet</span> <span class="o">*</span><span class="nx">QuestionSet</span><span class="p">)</span> <span class="nf">UnmarshalBSONValue</span><span class="p">(</span><span class="nx">t</span> <span class="nx">bsontype</span><span class="p">.</span><span class="nx">Type</span><span class="p">,</span> <span class="nx">data</span> <span class="p">[]</span><span class="kt">byte</span><span class="p">)</span> <span class="kt">error</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="kd">var</span> <span class="nx">rawData</span> <span class="nx">bson</span><span class="p">.</span><span class="nx">Raw</span>
</span></span><span class="line"><span class="cl">	<span class="nx">err</span> <span class="o">:=</span> <span class="nx">bson</span><span class="p">.</span><span class="nf">Unmarshal</span><span class="p">(</span><span class="nx">data</span><span class="p">,</span> <span class="o">&amp;</span><span class="nx">rawData</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="nx">err</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nx">err</span> <span class="p">=</span> <span class="nx">rawData</span><span class="p">.</span><span class="nf">Unmarshal</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">questionSet</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="nx">err</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="kd">var</span> <span class="nx">questions</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">Questions</span> <span class="nx">bson</span><span class="p">.</span><span class="nx">Raw</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nx">err</span> <span class="p">=</span> <span class="nx">rawData</span><span class="p">.</span><span class="nf">Unmarshal</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">questions</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="nx">err</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">switch</span> <span class="nx">questionPool</span><span class="p">.</span><span class="nx">GameName</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">case</span> <span class="s">&#34;drawlosseum&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">		<span class="nx">questionStructure</span> <span class="o">:=</span> <span class="nx">DrawlosseumQuestionsPool</span><span class="p">{}</span>
</span></span><span class="line"><span class="cl">		<span class="nx">err</span> <span class="p">=</span> <span class="nx">questions</span><span class="p">.</span><span class="nx">Questions</span><span class="p">.</span><span class="nf">Unmarshal</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">questionStructure</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="nx">questionPool</span><span class="p">.</span><span class="nx">Questions</span> <span class="p">=</span> <span class="nx">questionStructure</span>
</span></span><span class="line"><span class="cl">	<span class="k">case</span> <span class="s">&#34;quibly&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">		<span class="nx">questionStructure</span> <span class="o">:=</span> <span class="nx">QuiblyQuestionsPool</span><span class="p">{}</span>
</span></span><span class="line"><span class="cl">		<span class="nx">err</span> <span class="p">=</span> <span class="nx">questions</span><span class="p">.</span><span class="nx">Questions</span><span class="p">.</span><span class="nf">Unmarshal</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">questionStructure</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="nx">questionPool</span><span class="p">.</span><span class="nx">Questions</span> <span class="p">=</span> <span class="nx">questionStructure</span>
</span></span><span class="line"><span class="cl">	<span class="k">case</span> <span class="s">&#34;fibbing_it&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">		<span class="nx">questionStructure</span> <span class="o">:=</span> <span class="nx">FibbingItQuestionsPool</span><span class="p">{}</span>
</span></span><span class="line"><span class="cl">		<span class="nx">err</span> <span class="p">=</span> <span class="nx">questions</span><span class="p">.</span><span class="nx">Questions</span><span class="p">.</span><span class="nf">Unmarshal</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">questionStructure</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="nx">questionPool</span><span class="p">.</span><span class="nx">Questions</span> <span class="p">=</span> <span class="nx">questionStructure</span>
</span></span><span class="line"><span class="cl">	<span class="k">default</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="nx">errors</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;Unknown game name %s&#34;</span><span class="p">,</span> <span class="nx">questionPool</span><span class="p">.</span><span class="nx">GameName</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="nx">err</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This function looks very complicated so let&rsquo;s break it down and explain what&rsquo;s going on.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl">	<span class="kd">var</span> <span class="nx">rawData</span> <span class="nx">bson</span><span class="p">.</span><span class="nx">Raw</span>
</span></span><span class="line"><span class="cl">	<span class="nx">err</span> <span class="o">:=</span> <span class="nx">bson</span><span class="p">.</span><span class="nf">Unmarshal</span><span class="p">(</span><span class="nx">data</span><span class="p">,</span> <span class="o">&amp;</span><span class="nx">rawData</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="nx">err</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span></code></pre></div><p>First, we need to unmarshal the data into BSON raw data. We need the BSON raw data because it allows
us to partially unmarshal values. You can read more about it <a href="https://godoc.org/gopkg.in/mgo.v2/bson#Raw">here</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl">	<span class="nx">err</span> <span class="p">=</span> <span class="nx">rawData</span><span class="p">.</span><span class="nf">Unmarshal</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">questionSet</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="nx">err</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span></code></pre></div><p>Next, we need to unmarshal the data into the <code>QuestionSet</code> struct, this is mainly to fill all the other fields (<code>GameName</code>)
besides <code>Questions</code>.</p>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">Names</summary>
  
  The struct tags we&rsquo;ve defined <code>bson:&quot;x&quot;</code> should match the name of that field in the database, else the unmarshaling will not
work correctly i.e. the struct fields will be <code>nil</code>.
</details>

<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl">	<span class="kd">var</span> <span class="nx">questions</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nx">Questions</span> <span class="nx">bson</span><span class="p">.</span><span class="nx">Raw</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nx">err</span> <span class="p">=</span> <span class="nx">rawData</span><span class="p">.</span><span class="nf">Unmarshal</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">questions</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="nx">err</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span></code></pre></div><p>Now onto the part that deals with the <code>Questions</code> field. Here we get the raw BSON data only related to the <code>Questions</code> field. So it won&rsquo;t have anything
related to <code>GameName</code>. We create a &ldquo;temporary&rdquo; struct to hold this BSON data, with the same field name.</p>
<details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">BSON Struct Tags</summary>
  
  If your field has an <code>_</code> or something else a bit different, you should use the <code>bson</code> struct tags
to specify the name of the field in the database.
</details>

<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl">	<span class="k">switch</span> <span class="nx">questionPool</span><span class="p">.</span><span class="nx">GameName</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">case</span> <span class="s">&#34;drawlosseum&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">		<span class="nx">questionStructure</span> <span class="o">:=</span> <span class="nx">DrawlosseumQuestionsPool</span><span class="p">{}</span>
</span></span><span class="line"><span class="cl">		<span class="nx">err</span> <span class="p">=</span> <span class="nx">questions</span><span class="p">.</span><span class="nx">Questions</span><span class="p">.</span><span class="nf">Unmarshal</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">questionStructure</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="nx">questionPool</span><span class="p">.</span><span class="nx">Questions</span> <span class="p">=</span> <span class="nx">questionStructure</span>
</span></span><span class="line"><span class="cl">	<span class="k">case</span> <span class="s">&#34;quibly&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">		<span class="nx">questionStructure</span> <span class="o">:=</span> <span class="nx">QuiblyQuestionsPool</span><span class="p">{}</span>
</span></span><span class="line"><span class="cl">		<span class="nx">err</span> <span class="p">=</span> <span class="nx">questions</span><span class="p">.</span><span class="nx">Questions</span><span class="p">.</span><span class="nf">Unmarshal</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">questionStructure</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="nx">questionPool</span><span class="p">.</span><span class="nx">Questions</span> <span class="p">=</span> <span class="nx">questionStructure</span>
</span></span><span class="line"><span class="cl">	<span class="k">case</span> <span class="s">&#34;fibbing_it&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">		<span class="nx">questionStructure</span> <span class="o">:=</span> <span class="nx">FibbingItQuestionsPool</span><span class="p">{}</span>
</span></span><span class="line"><span class="cl">		<span class="nx">err</span> <span class="p">=</span> <span class="nx">questions</span><span class="p">.</span><span class="nx">Questions</span><span class="p">.</span><span class="nf">Unmarshal</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">questionStructure</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="nx">questionPool</span><span class="p">.</span><span class="nx">Questions</span> <span class="p">=</span> <span class="nx">questionStructure</span>
</span></span><span class="line"><span class="cl">	<span class="k">default</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="nx">errors</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;Unknown game name %s&#34;</span><span class="p">,</span> <span class="nx">questionPool</span><span class="p">.</span><span class="nx">GameName</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span></code></pre></div><p>Finally, let&rsquo;s take a look at the code that unmarshal our questions into the correct structs.
We will use a switch type statement. In this example, the <code>GameName</code> will determine how the questions
are stored. Each case looks something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl">	<span class="nx">questionStructure</span> <span class="o">:=</span> <span class="nx">DrawlosseumQuestionsPool</span><span class="p">{}</span>
</span></span><span class="line"><span class="cl">	<span class="nx">err</span> <span class="p">=</span> <span class="nx">questions</span><span class="p">.</span><span class="nx">Questions</span><span class="p">.</span><span class="nf">Unmarshal</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">questionStructure</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="nx">questionPool</span><span class="p">.</span><span class="nx">Questions</span> <span class="p">=</span> <span class="nx">questionStructure</span>
</span></span></code></pre></div><p>We define the correct struct to use. Then we unmarshal the raw BSON data into this struct. We then assign this struct
to the <code>questionPool</code> variable. This is what will be &ldquo;returned&rdquo; when we use <code>FindOne</code> function shown above.</p>
<p>That&rsquo;s it! We&rsquo;ve now created our custom unmarshal function for dealing with polymorphic data stored in MongoDB in
Golang.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://github.com/mongodb/mongo-go-driver/">Cover Photo</a></li>
<li><a href="https://gitlab.com/banter-bus/banter-bus-server/-/blob/39c05ef7e3097697e25343b47f4846d11f9e7ae5/src/core/models/user_models.go#L86-125">Example Project</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>What does yield do in Python</title>
      <link>https://haseebmajid.dev/posts/2020-11-30-what-does-yield-do-in-python/</link>
      <pubDate>Mon, 30 Nov 2020 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2020-11-30-what-does-yield-do-in-python/</guid>
      <description>&lt;p&gt;In this article, we will go over what the &lt;code&gt;yield&lt;/code&gt; keyword is used for. We will also cover how you can use a &lt;code&gt;yield&lt;/code&gt;
with a pytest fixture to allow us to &amp;ldquo;teardown&amp;rdquo; tests, after all of our tests have run. A common job being removing
test data from the database, so that next time your run the tests your tests won&amp;rsquo;t fail. Due to the database being
in a different (unexpected) state.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this article, we will go over what the <code>yield</code> keyword is used for. We will also cover how you can use a <code>yield</code>
with a pytest fixture to allow us to &ldquo;teardown&rdquo; tests, after all of our tests have run. A common job being removing
test data from the database, so that next time your run the tests your tests won&rsquo;t fail. Due to the database being
in a different (unexpected) state.</p>
<h2 id="background">Background</h2>
<h3 id="iterables--iterators">Iterables &amp; Iterators</h3>
<p>Before we can look at the <code>yield</code> keyword we will need to cover iterables and generators in Python. An &ldquo;iterable&rdquo; is
any Python object that can return its members one at a time, in a for-loop.</p>
<p>In Python we have functions called magic methods, there are methods like <code>__enter__</code> and <code>__exit__</code> defined within
objects. These are called &ldquo;magic&rdquo; methods because they are never directly called by the user. For an object to be
iterable, it needs to implement the <code>__iter__</code> method. If an object is iterable it can be passed to the <code>iter()</code>
function. The <code>iter()</code> function returns an iterator.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="err">❯</span> <span class="n">ipython</span>
</span></span><span class="line"><span class="cl"><span class="n">Python</span> <span class="mf">3.8.5</span> <span class="p">(</span><span class="n">default</span><span class="p">,</span> <span class="n">Jul</span> <span class="mi">28</span> <span class="mi">2020</span><span class="p">,</span> <span class="mi">12</span><span class="p">:</span><span class="mi">59</span><span class="p">:</span><span class="mi">40</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">Type</span> <span class="s1">&#39;copyright&#39;</span><span class="p">,</span> <span class="s1">&#39;credits&#39;</span> <span class="ow">or</span> <span class="s1">&#39;license&#39;</span> <span class="k">for</span> <span class="n">more</span> <span class="n">information</span>
</span></span><span class="line"><span class="cl"><span class="n">IPython</span> <span class="mf">7.14.0</span> <span class="o">--</span> <span class="n">An</span> <span class="n">enhanced</span> <span class="n">Interactive</span> <span class="n">Python</span><span class="o">.</span> <span class="n">Type</span> <span class="s1">&#39;?&#39;</span> <span class="k">for</span> <span class="n">help</span><span class="o">.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">In</span> <span class="p">[</span><span class="mi">1</span><span class="p">]:</span> <span class="nb">iter</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">])</span>
</span></span><span class="line"><span class="cl"><span class="n">Out</span><span class="p">[</span><span class="mi">1</span><span class="p">]:</span> <span class="o">&lt;</span><span class="n">list_iterator</span> <span class="n">at</span> <span class="mh">0x7f4c11556730</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">In</span> <span class="p">[</span><span class="mi">2</span><span class="p">]:</span> <span class="nb">iter</span><span class="p">(</span><span class="s2">&#34;hello&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">Out</span><span class="p">[</span><span class="mi">2</span><span class="p">]:</span> <span class="o">&lt;</span><span class="n">str_iterator</span> <span class="n">at</span> <span class="mh">0x7f4c11598c10</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">In</span> <span class="p">[</span><span class="mi">3</span><span class="p">]:</span> <span class="nb">iter</span><span class="p">(</span><span class="mi">42</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="o">---------------------------------------------------------------------------</span>
</span></span><span class="line"><span class="cl"><span class="ne">TypeError</span>                                 <span class="n">Traceback</span> <span class="p">(</span><span class="n">most</span> <span class="n">recent</span> <span class="n">call</span> <span class="n">last</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="o">&lt;</span><span class="n">ipython</span><span class="o">-</span><span class="nb">input</span><span class="o">-</span><span class="mi">3</span><span class="o">-</span><span class="n">ef50b48e4398</span><span class="o">&gt;</span> <span class="ow">in</span> <span class="o">&lt;</span><span class="n">module</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="o">----&gt;</span> <span class="mi">1</span> <span class="nb">iter</span><span class="p">(</span><span class="mi">42</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="ne">TypeError</span><span class="p">:</span> <span class="s1">&#39;int&#39;</span> <span class="nb">object</span> <span class="ow">is</span> <span class="ow">not</span> <span class="n">iterable</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">In</span> <span class="p">[</span><span class="mi">4</span><span class="p">]:</span>
</span></span></code></pre></div><p>An iterator is any object which has the <code>__next__</code> magic method defined. Whenever we use a for-loop
(or list comprehension), the <code>next</code> method is called automatically for us, to get the next item from
the iterable.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">In</span> <span class="p">[</span><span class="mi">5</span><span class="p">]:</span> <span class="n">hello_list</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;h&#34;</span><span class="p">,</span> <span class="s2">&#34;e&#34;</span><span class="p">,</span> <span class="s2">&#34;l&#34;</span><span class="p">,</span> <span class="s2">&#34;l&#34;</span><span class="p">,</span> <span class="s2">&#34;o&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">In</span> <span class="p">[</span><span class="mi">6</span><span class="p">]:</span> <span class="n">iterator</span> <span class="o">=</span> <span class="nb">iter</span><span class="p">(</span><span class="n">hello_list</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">In</span> <span class="p">[</span><span class="mi">7</span><span class="p">]:</span> <span class="nb">next</span><span class="p">(</span><span class="n">iterator</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">Out</span><span class="p">[</span><span class="mi">7</span><span class="p">]:</span> <span class="s1">&#39;h&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">In</span> <span class="p">[</span><span class="mi">8</span><span class="p">]:</span> <span class="nb">next</span><span class="p">(</span><span class="n">iterator</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">Out</span><span class="p">[</span><span class="mi">8</span><span class="p">]:</span> <span class="s1">&#39;e&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">In</span> <span class="p">[</span><span class="mi">9</span><span class="p">]:</span> <span class="nb">next</span><span class="p">(</span><span class="n">iterator</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">Out</span><span class="p">[</span><span class="mi">9</span><span class="p">]:</span> <span class="s1">&#39;l&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">In</span> <span class="p">[</span><span class="mi">10</span><span class="p">]:</span> <span class="nb">next</span><span class="p">(</span><span class="n">iterator</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">Out</span><span class="p">[</span><span class="mi">10</span><span class="p">]:</span> <span class="s1">&#39;l&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">In</span> <span class="p">[</span><span class="mi">11</span><span class="p">]:</span> <span class="nb">next</span><span class="p">(</span><span class="n">iterator</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">Out</span><span class="p">[</span><span class="mi">11</span><span class="p">]:</span> <span class="s1">&#39;o&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">In</span> <span class="p">[</span><span class="mi">12</span><span class="p">]:</span> <span class="nb">next</span><span class="p">(</span><span class="n">iterator</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="o">---------------------------------------------------------------------------</span>
</span></span><span class="line"><span class="cl"><span class="ne">StopIteration</span>                             <span class="n">Traceback</span> <span class="p">(</span><span class="n">most</span> <span class="n">recent</span> <span class="n">call</span> <span class="n">last</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="o">&lt;</span><span class="n">ipython</span><span class="o">-</span><span class="nb">input</span><span class="o">-</span><span class="mi">12</span><span class="o">-</span><span class="mi">4</span><span class="n">ce711c44abc</span><span class="o">&gt;</span> <span class="ow">in</span> <span class="o">&lt;</span><span class="n">module</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="o">----&gt;</span> <span class="mi">1</span> <span class="nb">next</span><span class="p">(</span><span class="n">iterator</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="ne">StopIteration</span><span class="p">:</span>
</span></span></code></pre></div><p>In summary, an iterable is an object that can be &ldquo;looped&rdquo; over and an iterator is an object which can
do the &ldquo;looping&rdquo; for us, it will keep track of the current state/index and move to the next item.
In the example above the <code>hello_list</code> is iterable and the <code>iterator</code> variable is the iterator.</p>
<h3 id="generators">Generators</h3>
<p>Generators are a special type of iterable, they differ from normal lists in two main ways:</p>
<ul>
<li>You can only iterate over them once</li>
<li>They don&rsquo;t store all of their values in memory</li>
</ul>
<p>So generators can be great when lists get very large.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">In</span> <span class="p">[</span><span class="mi">14</span><span class="p">]:</span> <span class="n">g</span> <span class="o">=</span> <span class="p">(</span><span class="n">x</span><span class="o">^</span><span class="mi">2</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">In</span> <span class="p">[</span><span class="mi">15</span><span class="p">]:</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">g</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span><span class="p">:</span>     <span class="nb">print</span><span class="p">(</span><span class="n">i</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span><span class="p">:</span>
</span></span><span class="line"><span class="cl"><span class="mi">2</span>
</span></span><span class="line"><span class="cl"><span class="mi">3</span>
</span></span><span class="line"><span class="cl"><span class="mi">0</span>
</span></span><span class="line"><span class="cl"><span class="mi">1</span>
</span></span><span class="line"><span class="cl"><span class="mi">6</span>
</span></span><span class="line"><span class="cl"><span class="mi">7</span>
</span></span><span class="line"><span class="cl"><span class="mi">4</span>
</span></span><span class="line"><span class="cl"><span class="mi">5</span>
</span></span><span class="line"><span class="cl"><span class="mi">10</span>
</span></span><span class="line"><span class="cl"><span class="mi">11</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">In</span> <span class="p">[</span><span class="mi">16</span><span class="p">]:</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">g</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span><span class="p">:</span>     <span class="nb">print</span><span class="p">(</span><span class="n">i</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span><span class="p">:</span>
</span></span></code></pre></div><h2 id="yield">Yield</h2>
<p>Now that we finally understand iterables and generators let&rsquo;s see how they relate to the <code>yield</code> keyword. <code>yield</code> can be
used like <code>return</code> except it will return a generator.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">In</span> <span class="p">[</span><span class="mi">17</span><span class="p">]:</span> <span class="k">def</span> <span class="nf">example</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span><span class="p">:</span>     <span class="k">yield</span> <span class="s2">&#34;A&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span><span class="p">:</span>     <span class="k">yield</span> <span class="s2">&#34;B&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span><span class="p">:</span>     <span class="k">yield</span> <span class="s2">&#34;C&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">In</span> <span class="p">[</span><span class="mi">18</span><span class="p">]:</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">example</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span><span class="p">:</span>     <span class="nb">print</span><span class="p">(</span><span class="n">i</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span><span class="p">:</span>
</span></span><span class="line"><span class="cl"><span class="n">A</span>
</span></span><span class="line"><span class="cl"><span class="n">B</span>
</span></span><span class="line"><span class="cl"><span class="n">C</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">In</span> <span class="p">[</span><span class="mi">22</span><span class="p">]:</span> <span class="n">example</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">Out</span><span class="p">[</span><span class="mi">22</span><span class="p">]:</span> <span class="o">&lt;</span><span class="n">generator</span> <span class="nb">object</span> <span class="n">example</span> <span class="n">at</span> <span class="mh">0x7f4c1147a0b0</span><span class="o">&gt;</span>
</span></span></code></pre></div><p>A good example of <code>yield</code> can be seen above, it differs from a return because it is smart enough to retain &ldquo;state&rdquo;
and resume where it left off in the function. We can see the same example with <code>return</code>. In this example there is only
a single item being returned so only &ldquo;A&rdquo; is being looped over.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">In</span> <span class="p">[</span><span class="mi">19</span><span class="p">]:</span> <span class="k">def</span> <span class="nf">example</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span><span class="p">:</span>     <span class="k">return</span> <span class="s2">&#34;A&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span><span class="p">:</span>     <span class="k">return</span> <span class="s2">&#34;B&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span><span class="p">:</span>     <span class="k">return</span> <span class="s2">&#34;C&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">In</span> <span class="p">[</span><span class="mi">20</span><span class="p">]:</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">example</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span><span class="p">:</span>     <span class="nb">print</span><span class="p">(</span><span class="n">i</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span><span class="p">:</span>
</span></span><span class="line"><span class="cl"><span class="n">A</span>
</span></span></code></pre></div><h2 id="pytest-example">Pytest Example</h2>
<p>One interesting use case of using the <code>yield</code> keyword is using it to run clean up tasks after running tests using
pytest. Pytest is a very popular testing framework in Python, it allows us to create a file called <code>conftest.py</code>.
Here we store common functions, fixtures shared between our tests.</p>
<p>In the example below, before any tests have run the <code>clean_up</code> fixture will be called, because we have given
it the <code>autouse=True</code> parameter. It will <code>yield</code>, and return a generator after all of our tests have finished
running. The print and the teardown tasks will then be run. This is useful for example when you want to clean up
your database after running tests that will add &ldquo;test&rdquo; data to it. Or in fact, any other type of teardown tasks
you need to run after all of your tests have finished running.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span><span class="p">(</span><span class="n">scope</span><span class="o">=</span><span class="s2">&#34;session&#34;</span><span class="p">,</span> <span class="n">autouse</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">clean_up</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="k">yield</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;teardown after yield&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">delete_database_collection</span><span class="p">()</span>
</span></span></code></pre></div><h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://realpython.com/python-for-loop/">Real Python</a> &ldquo;for&rdquo; loop</li>
<li><a href="https://realpython.com/introduction-to-python-generators/">Real Python</a> generators</li>
<li><a href="https://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do">SO Post</a></li>
<li><a href="https://dzone.com/articles/when-to-use-yield-instead-of-return-in-python">DZone Yield vs Return</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to add a ToC in Gatsby</title>
      <link>https://haseebmajid.dev/posts/2020-11-11-how-to-add-a-toc-in-gatsby/</link>
      <pubDate>Wed, 11 Nov 2020 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2020-11-11-how-to-add-a-toc-in-gatsby/</guid>
      <description>&lt;p&gt;A lot of people, I included, are using Gatsby to build their own blogs. One of the things I wanted to add to my blog
was a table of contents (ToC) 📝. A ToC will show you all the headings of an article and when you click on a heading it&amp;rsquo;ll
take you directly to that heading.
It&amp;rsquo;s a nice little feature to have on your blog, which makes it easier for users to navigate and find the information
they are looking for.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>A lot of people, I included, are using Gatsby to build their own blogs. One of the things I wanted to add to my blog
was a table of contents (ToC) 📝. A ToC will show you all the headings of an article and when you click on a heading it&rsquo;ll
take you directly to that heading.
It&rsquo;s a nice little feature to have on your blog, which makes it easier for users to navigate and find the information
they are looking for.</p>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden">
  <iframe src="https://yewtu.be/embed/YrUeiD4YO5E"
    style="position:absolute;top:0;left:0;width:100%;height:100%;border:0" allowfullscreen></iframe>
</div>

<h2 id="prerequisite">Prerequisite</h2>
<p>So before we get started you can find the <a href="https://gitlab.com/hmajid2301/">source code here</a>.
In this article, I will be using the
<a href="https://www.gatsbyjs.com/starters/gatsbyjs/gatsby-starter-blog/">gatsby-starter-blog</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># If you don&#39;t have the CLI installed, run this command.</span>
</span></span><span class="line"><span class="cl">npm -g install gatsby-cli
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">gatsby new my-gatsby-project https://github.com/gatsbyjs/gatsby-starter-blog
</span></span></code></pre></div><p>If you already have an existing Gatsby site, you can make the changes directly there instead of
using this starter.</p>
<h2 id="plugins">Plugins</h2>
<p>We need to get some extra plugins installed for the ToC to work properly.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">yarn add gatsby-remark-autolink-headers gatsby-plugin-emotion
</span></span></code></pre></div><p>The plugin <code>gatsby-remark-autolink-headers</code> turns all of the headers into anchor links. This means we can link to the
headers.</p>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">Emotion</summary>
  
  You only need to add the emotion plugin if you want to use emotionjs, which is a css-in-js solution.
You will see this later when we look at the <code>toc.js</code> component.
</details>

<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line hl"><span class="cl"><span class="sb">`gatsby-plugin-emotion`</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="sb">`gatsby-plugin-smoothscroll`</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">resolve</span><span class="o">:</span> <span class="sb">`gatsby-transformer-remark`</span><span class="p">,</span>
</span></span><span class="line hl"><span class="cl">    <span class="nx">options</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">plugins</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>            <span class="sb">`gatsby-remark-autolink-headers`</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="c1">// ...
</span></span></span></code></pre></div><p>Our header elements with the <code>autolinks</code> plugin will now look something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">h1</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;header-1&#34;</span> <span class="na">style</span><span class="o">=</span><span class="s">&#34;position:relative;&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;#header-1&#34;</span> <span class="na">aria-label</span><span class="o">=</span><span class="s">&#34;header 1 permalink&#34;</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;anchor before&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&gt;</span>...<span class="p">&lt;/</span><span class="nt">a</span>
</span></span><span class="line"><span class="cl">  <span class="p">&gt;</span>Header 1
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">h1</span><span class="p">&gt;</span>
</span></span></code></pre></div><h2 id="toc">ToC</h2>
<p>First, let&rsquo;s design the ToC element. This component is just a presentation component, it doesn&rsquo;t contain any state logic.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">styled</span> <span class="nx">from</span> <span class="s2">&#34;@emotion/styled&#34;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span> <span class="nx">from</span> <span class="s2">&#34;react&#34;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">tw</span> <span class="nx">from</span> <span class="s2">&#34;twin.macro&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">ToC</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">headings</span> <span class="p">})</span> <span class="p">=&gt;</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">Toc</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">Title</span><span class="p">&gt;</span><span class="nx">Table</span> <span class="k">of</span> <span class="nx">contents</span><span class="p">&lt;/</span><span class="nt">Title</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">InnerScroll</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span><span class="nx">headings</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">heading</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nx">heading</span><span class="p">.</span><span class="nx">depth</span> <span class="o">&gt;</span> <span class="mi">4</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="k">return</span> <span class="p">&lt;</span><span class="nt">div</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="p">&lt;</span><span class="nt">ToCElement</span> <span class="na">key</span><span class="o">=</span><span class="p">{</span><span class="nx">heading</span><span class="p">.</span><span class="nx">value</span><span class="p">}&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">ToCLink</span>
</span></span><span class="line"><span class="cl">              <span class="na">href</span><span class="o">=</span><span class="p">{</span><span class="sb">`#</span><span class="si">${</span><span class="nx">heading</span><span class="p">.</span><span class="nx">value</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/\s+/g</span><span class="p">,</span> <span class="s2">&#34;-&#34;</span><span class="p">).</span><span class="nx">toLowerCase</span><span class="p">()</span><span class="si">}</span><span class="sb">`</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">              <span class="p">{</span><span class="nx">heading</span><span class="p">.</span><span class="nx">value</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;/</span><span class="nt">ToCLink</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">          <span class="p">&lt;/</span><span class="nt">ToCElement</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">})}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">InnerScroll</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">Toc</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">Toc</span> <span class="o">=</span> <span class="nx">styled</span><span class="p">.</span><span class="nx">ul</span><span class="sb">`
</span></span></span><span class="line"><span class="cl"><span class="sb">  </span><span class="si">${</span><span class="nx">tw</span><span class="sb">`bg-white fixed hidden lg:flex flex-col rounded p-3 my-3`</span><span class="si">}</span><span class="sb">;
</span></span></span><span class="line"><span class="cl"><span class="sb">  width: 20rem;
</span></span></span><span class="line"><span class="cl"><span class="sb">  left: calc(50% + 400px);
</span></span></span><span class="line"><span class="cl"><span class="sb">  top: 80px;
</span></span></span><span class="line"><span class="cl"><span class="sb">  max-height: 30vh;
</span></span></span><span class="line"><span class="cl"><span class="sb">`</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">Title</span> <span class="o">=</span> <span class="nx">tw</span><span class="p">.</span><span class="nx">h2</span><span class="sb">`text-2xl mb-2`</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">ToCElement</span> <span class="o">=</span> <span class="nx">tw</span><span class="p">.</span><span class="nx">li</span><span class="sb">`p-1 leading-5 ml-4 mb-4 mr-4 leading-3 list-none`</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">ToCLink</span> <span class="o">=</span> <span class="nx">tw</span><span class="p">.</span><span class="nx">a</span><span class="sb">`hover:text-black transition duration-300 no-underline`</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">InnerScroll</span> <span class="o">=</span> <span class="nx">styled</span><span class="p">.</span><span class="nx">div</span><span class="sb">`
</span></span></span><span class="line"><span class="cl"><span class="sb">  scrollbar-width: thin;
</span></span></span><span class="line"><span class="cl"><span class="sb">  scrollbar-color: #367ee9 rgba(48, 113, 209, 0.3);
</span></span></span><span class="line"><span class="cl"><span class="sb">  overflow: hidden auto;
</span></span></span><span class="line"><span class="cl"><span class="sb">`</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">ToC</span>
</span></span></code></pre></div><p>Let&rsquo;s break this component down. It receives a <code>heading</code> props, which it expects to be a list of the <code>headings</code>
from the markdown documents. A heading is an element starting with <code>#</code>, the more <code>#</code>s the lower the heading, for example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl"><span class="gh"># Heading 1
</span></span></span><span class="line"><span class="cl"><span class="gh"></span>
</span></span><span class="line"><span class="cl"><span class="gu">## Heading 2
</span></span></span><span class="line"><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="cl"><span class="gu">### Heading 3
</span></span></span></code></pre></div><p>We use the <code>heading.map</code> which will create an element for each of the <code>headings</code> in the list. If it&rsquo;s a &ldquo;heading 5&rdquo; or lower,
we will simply return an empty div, <code>heading.depth &gt; 4</code>. This is so that the ToC doesn&rsquo;t become too &ldquo;big&rdquo; and
which would make it harder to use/navigate.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">ToC</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">headings</span> <span class="p">})</span> <span class="p">=&gt;</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="o">&lt;</span><span class="nx">Toc</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="o">&lt;</span><span class="nx">Title</span><span class="o">&gt;</span><span class="nx">Table</span> <span class="k">of</span> <span class="nx">contents</span><span class="o">&lt;</span><span class="err">/Title&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="o">&lt;</span><span class="nx">InnerScroll</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span><span class="nx">headings</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">heading</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nx">heading</span><span class="p">.</span><span class="nx">depth</span> <span class="o">&gt;</span> <span class="mi">4</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="k">return</span> <span class="o">&lt;</span><span class="nx">div</span> <span class="o">/&gt;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="o">&lt;</span><span class="nx">ToCElement</span> <span class="nx">key</span><span class="o">=</span><span class="p">{</span><span class="nx">heading</span><span class="p">.</span><span class="nx">value</span><span class="p">}</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="o">&lt;</span><span class="nx">ToCLink</span>
</span></span><span class="line"><span class="cl">              <span class="nx">href</span><span class="o">=</span><span class="p">{</span><span class="sb">`#</span><span class="si">${</span><span class="nx">heading</span><span class="p">.</span><span class="nx">value</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/\s+/g</span><span class="p">,</span> <span class="s2">&#34;-&#34;</span><span class="p">).</span><span class="nx">toLowerCase</span><span class="p">()</span><span class="si">}</span><span class="sb">`</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="o">&gt;</span>
</span></span><span class="line"><span class="cl">              <span class="p">{</span><span class="nx">heading</span><span class="p">.</span><span class="nx">value</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="o">&lt;</span><span class="err">/ToCLink&gt;</span>
</span></span><span class="line"><span class="cl">          <span class="o">&lt;</span><span class="err">/ToCElement&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="p">})}</span>
</span></span><span class="line"><span class="cl">    <span class="o">&lt;</span><span class="err">/InnerScroll&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="o">&lt;</span><span class="err">/Toc&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span></code></pre></div><p>If it&rsquo;s a heading 1-4, we create a list element (<code>&lt;li&gt;</code>) with a link (<code>&lt;a&gt;</code>) inside of it. This will be a single
heading within our ToC. Below is an example ToC:</p>
<p><img
        loading="lazy"
        src="/posts/2020-11-11-how-to-add-a-toc-in-gatsby/images/example_toc.png"
        type=""
        alt="Example ToC"
        
      /></p>
<p>The heading data for the ToC above will look something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">headings</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">value</span><span class="o">:</span> <span class="s2">&#34;Header 1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">depth</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">value</span><span class="o">:</span> <span class="s2">&#34;Header 2&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">depth</span><span class="o">:</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">];</span>
</span></span></code></pre></div><p>As discussed earlier we are using the <code>autolink</code> headers plugin. This plugin auto-generates anchor links for all of our header. We will use the <code>href</code> attribute to link to these headers in our ToC.</p>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">href</summary>
  
  <p>The <code>href</code> link we replace all the whitespace with <code>-</code> so <code>&quot;Heading 1&quot;</code> becomes the anchor link <code>#heading-1</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">ToCLink</span> <span class="na">href</span><span class="o">=</span><span class="p">{</span><span class="sb">`#</span><span class="si">${</span><span class="nx">heading</span><span class="p">.</span><span class="nx">value</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/\s+/g</span><span class="p">,</span> <span class="s2">&#34;-&#34;</span><span class="p">).</span><span class="nx">toLowerCase</span><span class="p">()</span><span class="si">}</span><span class="sb">`</span><span class="p">}&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span><span class="nx">heading</span><span class="p">.</span><span class="nx">value</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">ToCLink</span><span class="p">&gt;</span>
</span></span></code></pre></div>
</details>

<h3 id="twin-macro--emotionjs">Twin Macro &amp; EmotionJS</h3>
<p>Now in the above <code>ToC</code> component, you see elements like <code>&lt;ToCElement&gt;</code>, <code>&lt;InnerScroll&gt;</code> and <code>&lt;ToC&gt;</code>.
Where are these components coming from? Well, this is why I said we need to use the gatsby emotion plugin.
This is the css-in-js components, these components above are twin.macro or emotionjs components.
To use it within our code run the following commands:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">yarn add twin.macro @emotion/core @emotion/styled
</span></span><span class="line"><span class="cl">npx tailwind init
</span></span><span class="line"><span class="cl">vim package.json
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="s2">&#34;babelMacros&#34;</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;twin&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;config&#34;</span><span class="p">:</span> <span class="s2">&#34;tailwind.config.js&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;preset&#34;</span><span class="p">:</span> <span class="s2">&#34;emotion&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;dataTwProp&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;debugPlugins&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;debug&#34;</span><span class="p">:</span> <span class="kc">false</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><details
  class="notice info"
  open="true"
>
    <summary class="notice-title">TailwindCSS with Gatsby</summary>
  
  Gatsby have a good tutorial <a href="https://www.gatsbyjs.com/docs/tailwind-css/">here</a>, on how to integrate
TailwindCSS with a Gatsby site.
</details>

<p>The <code>twin.macro</code> library allows us to use <a href="https://tailwindcss.com/"><code>TailwindCSS</code></a>. Tailwind provides us with
many pre-generated classes that we can then leverage within our code. Here I am assuming you are somewhat familiar with how it works.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">Toc</span> <span class="o">=</span> <span class="nx">styled</span><span class="p">.</span><span class="nx">ul</span><span class="sb">`
</span></span></span><span class="line"><span class="cl"><span class="sb">  </span><span class="si">${</span><span class="nx">tw</span><span class="sb">`bg-white fixed hidden lg:flex flex-col rounded p-3 my-3`</span><span class="si">}</span><span class="sb">;
</span></span></span><span class="line"><span class="cl"><span class="sb">  width: 20rem;
</span></span></span><span class="line"><span class="cl"><span class="sb">  left: calc(50% + 400px);
</span></span></span><span class="line"><span class="cl"><span class="sb">  top: 80px;
</span></span></span><span class="line"><span class="cl"><span class="sb">  max-height: 30vh;
</span></span></span><span class="line"><span class="cl"><span class="sb">`</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">Title</span> <span class="o">=</span> <span class="nx">tw</span><span class="p">.</span><span class="nx">h2</span><span class="sb">`text-2xl mb-2`</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">ToCElement</span> <span class="o">=</span> <span class="nx">tw</span><span class="p">.</span><span class="nx">li</span><span class="sb">`p-1 leading-5 ml-4 mb-4 mr-4 leading-3 list-none`</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">ToCLink</span> <span class="o">=</span> <span class="nx">tw</span><span class="p">.</span><span class="nx">a</span><span class="sb">`hover:text-black transition duration-300 no-underline`</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">InnerScroll</span> <span class="o">=</span> <span class="nx">styled</span><span class="p">.</span><span class="nx">div</span><span class="sb">`
</span></span></span><span class="line"><span class="cl"><span class="sb">  scrollbar-width: thin;
</span></span></span><span class="line"><span class="cl"><span class="sb">  scrollbar-color: #367ee9 rgba(48, 113, 209, 0.3);
</span></span></span><span class="line"><span class="cl"><span class="sb">  overflow: hidden auto;
</span></span></span><span class="line"><span class="cl"><span class="sb">`</span><span class="p">;</span>
</span></span></code></pre></div><p>This is how we can style the scrollbar. The first colour is the colour of the scrollbar and the second colour is the
the background colour of the scrollbar.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">InnerScroll</span> <span class="o">=</span> <span class="nx">styled</span><span class="p">.</span><span class="nx">div</span><span class="sb">`
</span></span></span><span class="line"><span class="cl"><span class="sb">  scrollbar-width: thin;
</span></span></span><span class="line"><span class="cl"><span class="sb">  scrollbar-color: #367ee9 rgba(48, 113, 209, 0.3);
</span></span></span><span class="line"><span class="cl"><span class="sb">  overflow: hidden auto;
</span></span></span><span class="line"><span class="cl"><span class="sb">`</span><span class="p">;</span>
</span></span></code></pre></div><details
  class="notice info"
  open="true"
>
    <summary class="notice-title">div</summary>
  
  <p>Gatsby have a good tutorial <a href="https://www.gatsbyjs.com/docs/tailwind-css/">here</a>, on how to integrate
The <code>styled.div</code> this means <code>InnerScroll</code> when translated to HTML code will be <code>&lt;div&gt;</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;css-91zyin-InnerScroll eqpue8b4&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">li</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;css-12965kf-ToCElement eqpue8b2&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;#header-1&#34;</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;css-14n9u33-ToCLink eqpue8b3&#34;</span><span class="p">&gt;</span>Header 1<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">li</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span></code></pre></div>
</details>

<p>Another interesting component to look at is the <code>ToC</code>. This combined twin.macro and emotionjs
so <code>width</code> CSS is using emotionjs and we are using twin.macro with <code>{tw</code>&hellip;<code>}</code>. Where we fill in
the <code>tw</code> with the tailwind styles we want to apply. In the example below, <code>fixed</code> will make
the position of the element fixed.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">Toc</span> <span class="o">=</span> <span class="nx">styled</span><span class="p">.</span><span class="nx">ul</span><span class="sb">`
</span></span></span><span class="line"><span class="cl"><span class="sb">  </span><span class="si">${</span><span class="nx">tw</span><span class="sb">`bg-white fixed hidden lg:flex flex-col rounded p-3 my-3`</span><span class="si">}</span><span class="sb">;
</span></span></span><span class="line"><span class="cl"><span class="sb">  width: 20rem;
</span></span></span><span class="line"><span class="cl"><span class="sb">  left: calc(50% + 400px);
</span></span></span><span class="line"><span class="cl"><span class="sb">  top: 80px;
</span></span></span><span class="line"><span class="cl"><span class="sb">  max-height: 30vh;
</span></span></span><span class="line"><span class="cl"><span class="sb">`</span><span class="p">;</span>
</span></span></code></pre></div><h2 id="global-style">Global Style</h2>
<p>One small change we need to make to allow our scrolling to be smoother is in our global styles,
whether that should be a CSS file or a css-in-js etc. In this example, it&rsquo;ll be the <code>style.css</code> file that comes with
the start. We need to add the following properties:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="nt">html</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">scroll-behavior</span><span class="p">:</span> <span class="kc">smooth</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="err">//</span> <span class="err">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This CSS property will stop the scrolling from feeling jerky and instead will be far smoother. So instead of jumping to the header, we click on a header in the <code>ToC</code> and it&rsquo;ll scroll smoothly to that header.</p>
<h2 id="blog-template">Blog Template</h2>
<p>Finally, we need to add the ToC element to our blog template.</p>
<h3 id="gatsby-node">Gatsby Node</h3>
<p>Remember that with Gatsby in the <code>gatsby-node.js</code> file, we can create a new page for each markdown file found. Here
is the logic that creates a page for each markdown file found by the <code>markdown-remark</code> plugin. As you can see, we use
<code>blog-post.js</code> file as the template for each of our blog posts.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">exports</span><span class="p">.</span><span class="nx">createPages</span> <span class="o">=</span> <span class="kr">async</span> <span class="p">({</span> <span class="nx">graphql</span><span class="p">,</span> <span class="nx">actions</span><span class="p">,</span> <span class="nx">reporter</span> <span class="p">})</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="p">{</span> <span class="nx">createPage</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">actions</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Define a template for blog post
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="kr">const</span> <span class="nx">blogPost</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="sb">`./src/templates/blog-post.js`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Get all markdown blog posts sorted by date
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="kr">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">graphql</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="sb">`
</span></span></span><span class="line"><span class="cl"><span class="sb">      {
</span></span></span><span class="line"><span class="cl"><span class="sb">        allMarkdownRemark(
</span></span></span><span class="line"><span class="cl"><span class="sb">          sort: { fields: [frontmatter___date], order: ASC }
</span></span></span><span class="line"><span class="cl"><span class="sb">          limit: 1000
</span></span></span><span class="line"><span class="cl"><span class="sb">        ) {
</span></span></span><span class="line"><span class="cl"><span class="sb">          nodes {
</span></span></span><span class="line"><span class="cl"><span class="sb">            id
</span></span></span><span class="line"><span class="cl"><span class="sb">            fields {
</span></span></span><span class="line"><span class="cl"><span class="sb">              slug
</span></span></span><span class="line"><span class="cl"><span class="sb">            }
</span></span></span><span class="line"><span class="cl"><span class="sb">          }
</span></span></span><span class="line"><span class="cl"><span class="sb">        }
</span></span></span><span class="line"><span class="cl"><span class="sb">      }
</span></span></span><span class="line"><span class="cl"><span class="sb">    `</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">posts</span> <span class="o">=</span> <span class="nx">result</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">allMarkdownRemark</span><span class="p">.</span><span class="nx">nodes</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Create blog posts pages
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="c1">// But only if there&#39;s at least one markdown file found at &#34;content/blog&#34; (defined in gatsby-config.js)
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="c1">// `context` is available in the template as a prop and as a variable in GraphQL
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">posts</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">posts</span><span class="p">.</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">post</span><span class="p">,</span> <span class="nx">index</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">createPage</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">        <span class="nx">path</span><span class="o">:</span> <span class="nx">post</span><span class="p">.</span><span class="nx">fields</span><span class="p">.</span><span class="nx">slug</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">component</span><span class="o">:</span> <span class="nx">blogPost</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="p">});</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><h3 id="gatsby-config">Gatsby Config</h3>
<p>To allow the remark plugin to &ldquo;see&rdquo; the markdown files, we need to source them. There are a few ways to do this,
I normally use <a href="/blog/gatsby-articles-git-gitlab-ci">git to source my plugins</a>. In this case, we will add all
the markdown files in the <code>content/blog</code> folder.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">resolve</span><span class="o">:</span> <span class="sb">`gatsby-source-filesystem`</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">options</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">path</span><span class="o">:</span> <span class="sb">`</span><span class="si">${</span><span class="nx">__dirname</span><span class="si">}</span><span class="sb">/content/blog`</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">name</span><span class="o">:</span> <span class="sb">`blog`</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="blog-post">Blog Post</h3>
<p>So now back to our <code>blog-post.js</code>, let us add our <code>ToC</code> component to the blog post template.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">ToC</span> <span class="nx">from</span> <span class="s2">&#34;../components/toc&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">post</span> <span class="o">=</span> <span class="nx">data</span><span class="p">.</span><span class="nx">markdownRemark</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">Layout</span> <span class="na">location</span><span class="o">=</span><span class="p">{</span><span class="nx">location</span><span class="p">}</span> <span class="na">title</span><span class="o">=</span><span class="p">{</span><span class="nx">siteTitle</span><span class="p">}&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">article</span> <span class="na">className</span><span class="o">=</span><span class="s">&#34;blog-post&#34;</span> <span class="na">itemScope</span> <span class="na">itemType</span><span class="o">=</span><span class="s">&#34;http://schema.org/Article&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">ToC</span> <span class="na">headings</span><span class="o">=</span><span class="p">{</span><span class="nx">post</span><span class="p">.</span><span class="nx">headings</span><span class="p">}</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;/</span><span class="nt">article</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">Layout</span><span class="p">&gt;;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></div><p>Let&rsquo;s also adjust the GraphQL query so we can get the heading data that the ToC component requires.
Add the <code>headings</code> field to get the value and the depth fields.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-graphql" data-lang="graphql"><span class="line"><span class="cl"><span class="py">markdownRemark</span><span class="p">(</span><span class="py">id</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nc">eq</span><span class="p">:</span><span class="w"> </span><span class="nv">$id</span><span class="w"> </span><span class="p">})</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nc">id</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="py">excerpt</span><span class="p">(</span><span class="py">pruneLength</span><span class="p">:</span><span class="w"> </span><span class="nc">160</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="py">html</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="py">headings</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="py">value</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="py">depth</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="py">frontmatter</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="py">title</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="py">date</span><span class="p">(</span><span class="py">formatString</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;MMMM DD, YYYY&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nc">description</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>That&rsquo;s that we added a ToC to our Gatsby Site. We can a bunch of other things to improve it. Such as
styling it to make it look better. I also hide my ToC when the width decreases. So you only see a ToC when
browsing the site on a Laptop/Desktop.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2020-11-11-how-to-add-a-toc-in-gatsby/source_code">Example source code</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How DNS works with Docker</title>
      <link>https://haseebmajid.dev/posts/2020-10-27-how-dns-works-with-docker/</link>
      <pubDate>Tue, 27 Oct 2020 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2020-10-27-how-dns-works-with-docker/</guid>
      <description>&lt;p&gt;In this article, we will briefly go over what DNS (domain name system) is and explain how it is used in conjunction
with Docker 🐳.&lt;/p&gt;
&lt;h2 id=&#34;dns&#34;&gt;DNS&lt;/h2&gt;
&lt;p&gt;You can think of DNS like a phonebook, except instead of people&amp;rsquo;s name and phone numbers, it stores domains names and
IP addresses (this can be either IPv4 or IPv6). Where a domain name is used to identify resources i.e. &lt;code&gt;google.com&lt;/code&gt; is a
domain name. This is how DNS works:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this article, we will briefly go over what DNS (domain name system) is and explain how it is used in conjunction
with Docker 🐳.</p>
<h2 id="dns">DNS</h2>
<p>You can think of DNS like a phonebook, except instead of people&rsquo;s name and phone numbers, it stores domains names and
IP addresses (this can be either IPv4 or IPv6). Where a domain name is used to identify resources i.e. <code>google.com</code> is a
domain name. This is how DNS works:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">google.com:     8.8.8.8
</span></span><span class="line"><span class="cl">cloudflare.com: 1.1.1.1
</span></span></code></pre></div><h3 id="example">Example</h3>
<p>You can manually send a DNS request (and get a response) using the <code>dig</code> command. So for example, we can do something
like this.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">dig +short google.com
</span></span><span class="line"><span class="cl">172.217.169.78
</span></span></code></pre></div><h3 id="records">Records</h3>
<p>Each DNS entry can be of varying types, some of the most common DNS types (referred to as records) are:</p>
<p>A: Points a domain name to an IPv4 address i.e. <code>8.8.8.8</code>
AAAA: Same as an A record except points to an IPv6 address i.e. <code>2001:db8:0:1</code>
CNAME: Canonical Name points one domain to another domain name, one common use case is to point <code>www.example.com</code> -&gt; <code>example.com</code>. This way we only need to update the A record of <code>example.com</code>, not both domains.</p>
<h4 id="aaaa-example">AAAA Example</h4>
<p>To specify a AAAA (quad A) record we can do something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">dig +short google.com AAAA
</span></span><span class="line"><span class="cl">2a00:1450:4009:810::200e
</span></span></code></pre></div><details
  class="notice important"
  open="true"
>
    <summary class="notice-title">More Details</summary>
  
  In a future article, I will do a deeper dive into the mechanics of how DNS works and the actual process of converting a domain name to an IP address.
</details>

<p>So that&rsquo;s DNS in a nutshell! On to how it relates to Docker.</p>
<details
  class="notice tip"
  open="true"
>
    <summary class="notice-title">tl:dr</summary>
  
  DNS is a system used to convert domain names into IP addresses because it&rsquo;s much easier for humans to remember names as compared with numbers.
</details>

<h2 id="docker">Docker</h2>
<p>For the sake of this article, we will be using the following docker-compose file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;3.5&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">web_server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">nginx</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">build</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">context</span><span class="p">:</span><span class="w"> </span><span class="l">.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">dockerfile</span><span class="p">:</span><span class="w"> </span><span class="l">docker/nginx/Dockerfile</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">80</span><span class="p">:</span><span class="m">80</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">app</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">app</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">flask</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">build</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">context</span><span class="p">:</span><span class="w"> </span><span class="l">.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">dockerfile</span><span class="p">:</span><span class="w"> </span><span class="l">docker/flask/Dockerfile</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">env_file</span><span class="p">:</span><span class="w"> </span><span class="l">docker/database.conf</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">expose</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">8080</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">database</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">postgres</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">postgres:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">env_file</span><span class="p">:</span><span class="w"> </span><span class="l">docker/database.conf</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">5432</span><span class="p">:</span><span class="m">5432</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">db_volume:/var/lib/postgresql</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">db_volume</span><span class="p">:</span><span class="w">
</span></span></span></code></pre></div><p>It will create three containers, Nginx, a flask app and a Postgres database, when we run <code>docker-compose up --build</code>, in particular take <strong>note</strong> of the <code>container_name</code>(s): <code>postgres</code>, <code>nginx</code>, <code>flask</code>.</p>
<details
  class="notice tip"
  open="true"
>
    <summary class="notice-title">Source Code</summary>
  
  The source code for those Docker containers can be found
<a href="https://gitlab.com/hmajid2301/articles/-/tree/master/7.%20Multi%20Docker%20Container%20with%20Nginx%2C%20Flask%20and%C2%A0MySQL/source_code">here</a>
</details>

<h3 id="nginx">Nginx</h3>
<p>Our <code>nginx</code> config file looks something like:</p>
<pre tabindex="0"><code class="language-conf" data-lang="conf">server {
  listen 80;
  server_name _;

  location / {
    try_files $uri @app;
  }

  location @app {
    include /etc/nginx/uwsgi_params;
    uwsgi_pass flask:8080;
  }
}</code></pre>
<p>This Nginx configuration file tells Nginx to pass any requests sent on <code>/</code> path to the
uwsgi server running in the <code>flask</code> docker container.
Now taking a look at the <code>location @app</code> section you&rsquo;ll notice for <code>uwsgi_pass</code> we don&rsquo;t specify an IP address to send the requests
to. Instead, we use the container name, this is because within Docker containers we don&rsquo;t have to specify the other Docker
container&rsquo;s IP address to connect to it we can specify the container name. Docker&rsquo;s DNS will resolve the name into an IP address for us.</p>
<h3 id="nginx-example">Nginx Example</h3>
<p>So if I open a shell on the <code>nginx</code> container:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker <span class="nb">exec</span> -it nginx bash
</span></span></code></pre></div><p>Then we can do something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">apt update <span class="o">&amp;&amp;</span> apt install dnsutils
</span></span><span class="line"><span class="cl">dig flask +short
</span></span><span class="line"><span class="cl">172.23.0.3
</span></span></code></pre></div><p>This is particularly useful because Docker containers get assigned an IP if you don&rsquo;t specify one
(in the <code>docker-compose.yml</code>) file. Taking a look at the IP assigned to the <code>flask</code>
the container matches the IP address returned by the dig command.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker inspect -f <span class="s1">&#39;{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}&#39;</span> flask
</span></span><span class="line"><span class="cl">172.23.0.3
</span></span></code></pre></div><h3 id="flask-example">Flask Example</h3>
<p>Similarly in the <code>flask</code> container if we want to connect to the <code>postgres</code> database, we can just specify the host
using the container name <code>postgres</code> rather than an IP in our connection URI. As shown in the example below:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">DATABASE_CONNECTION_URI</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">&#39;postgresql+psycopg2://</span><span class="si">{</span><span class="n">user</span><span class="si">}</span><span class="s1">:</span><span class="si">{</span><span class="n">password</span><span class="si">}</span><span class="s1">@postgres:5432/</span><span class="si">{</span><span class="n">database</span><span class="si">}</span><span class="s1">&#39;</span>
</span></span></code></pre></div><details
  class="notice info"
  open="true"
>
    <summary class="notice-title">Example</summary>
  
  The example above is a URI used by the SQLAlchemy library to connect to the Postgres database.
</details>

<h2 id="deep-diver">Deep Diver</h2>
<p>Let&rsquo;s take a slightly closer look into Docker&rsquo;s architecture to understand what is going on here.</p>
<h3 id="docker-engine--explained">Docker Engine 🏭 Explained</h3>
<blockquote>
<p>Docker Engine is an open-source containerization technology for building and containerizing your applications. - <a href="https://docs.docker.com/engine/">https://docs.docker.com/engine/</a></p>
</blockquote>
<p>It contains the following components:</p>
<ul>
<li>A server with a long-running daemon process dockerd</li>
<li>APIs which specify interfaces that programs can use to talk to and instruct the Docker daemon</li>
<li>A command-line interface (CLI) client docker</li>
</ul>
<p>When we install Docker we are also installing the Docker Engine.</p>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">More Details</summary>
  
  In a future article, I will do a deeper dive into the Docker Engine as well 🐳.
</details>

<p>Briefly, how it works is we use the CLI i.e. <code>docker run</code>/<code>docker-compose</code>, which makes
API requests (on our behalf) to the Docker daemon. The Docker daemon then interacts with containerd, which is responsible for the creation/deletion of our containers. Essentially containerd is a container supervisor.</p>
<h3 id="docker-engine-and-dns">Docker Engine and DNS</h3>
<p>Now how does Docker Engine relate to DNS? As long as the two containers are on the same
network we can use the container name and resolve it using DNS. Each Docker container has a DNS resolver that forwards
DNS queries to Docker Engine, which acts as a DNS server. Docker
Engine then checks if the DNS query belongs to a container on the network that the requested container belongs to.
If it does, then Docker Engine looks up the IP address that matches a container name in its key-value store and
returns that IP back to the requesting container.</p>
<p><img
        loading="lazy"
        src="/posts/2020-10-27-how-dns-works-with-docker/images/docker_engine.png"
        type=""
        alt="Docker Engine DNS"
        
      /></p>
<details
  class="notice tip"
  open="true"
>
    <summary class="notice-title">Normal Queries</summary>
  
  For all other DNS queries the Docker Engine will use the host machine&rsquo;s DNS settings,
unless overwritten (explained below in the <code>Misc</code> section).
</details>

<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">Daemon vs Engine</summary>
  
  <blockquote>
<p>Docker Daemon checks the client request and communicates with the Docker components to perform a service whereas, Docker Engine or Docker is the base engine installed on your host machine to build and run containers using Docker components and services - Anjali Nair, <a href="https://www.quora.com/What-is-the-difference-between-the-Docker-Engine-and-Docker-Daemon">Quora</a></p>
</blockquote>
</details>

<h2 id="misc">Misc</h2>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">Docker DNS Settings</summary>
  
  We can customise Docker&rsquo;s default DNS settings by using the <code>--dns</code> flag, for example, to use Google&rsquo;s DNS you could
go <code>--dns 8.8.8.8</code>. You can also provide your DNS records for the container to use by using the <code>--extra_hosts</code> flag.
For example <code>--extra_hosts somehost:162.242.195.82</code>.
</details>

<details
  class="notice warnings"
  open="true"
>
    <summary class="notice-title">Docker DNS Settings</summary>
  
  Custom hosts defined in the <code>/etc/hosts</code> file are ignored. They must be passed in using the <code>extra_hosts</code> flag.
</details>

<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://www.cloudflare.com/en-gb/learning/dns/what-is-dns/">What is DNS?</a> by CloudFlare</li>
<li><a href="https://www.bluehost.com/help/article/dns-records-explained">DNS Records</a> Explained</li>
<li><a href="https://www.serverwatch.com/server-news/how-docker-engine-works-to-enable-containers/">Docker Engine</a></li>
<li><a href="https://stackoverflow.com/questions/41645665/how-containerd-compares-to-runc">Docker in detail</a> SO Post</li>
<li><a href="https://success.mirantis.com/article/networking">Docker Swarm Architecture</a> (relevant to normal Docker)</li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How SOCKS proxies work and using ProxyChains</title>
      <link>https://haseebmajid.dev/posts/2020-10-10-how-socks-proxies-work-and-using-proxychains/</link>
      <pubDate>Sat, 10 Oct 2020 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2020-10-10-how-socks-proxies-work-and-using-proxychains/</guid>
      <description>&lt;p&gt;In this article, we will go over how you can use &lt;code&gt;proxychains&lt;/code&gt; to proxy our traffic through a socks proxy.&lt;/p&gt;
&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;p&gt;Recently, like everyone else, I&amp;rsquo;ve been working from home a lot more often. This means to access resources at work
I need to use a VPN. However, to access some resources, such as production servers from my local machine, I need to
use a SOCKS5 proxy. Without using a SOCKS proxy, I would need to do something shown in the diagram below.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this article, we will go over how you can use <code>proxychains</code> to proxy our traffic through a socks proxy.</p>
<h2 id="background">Background</h2>
<p>Recently, like everyone else, I&rsquo;ve been working from home a lot more often. This means to access resources at work
I need to use a VPN. However, to access some resources, such as production servers from my local machine, I need to
use a SOCKS5 proxy. Without using a SOCKS proxy, I would need to do something shown in the diagram below.</p>
<h3 id="example-setup">Example Setup</h3>
<div class="mermaid">graph LR
  A[Local Machine] -->|ssh| B[Server A]
  B -->|ssh| C[Server B]
  subgraph Firewall
    C
  end
</div>

<p>First, I would need to SSH onto an intermediate server (<code>Server A</code>), which I have connectivity to from my local machine.
Then on that intermediate server, I would need to SSH onto the production server. So this intermediate server needs
to have connectivity to the <code>Server B</code> as well. As you can see <code>Server B</code> is behind a firewall, in this example, the
firewall will only allow traffic from <code>Server A</code> to ingress to <code>Server B</code>. So we cannot connect directly from <code>Server A</code>.</p>
<p>Another reason this setup is sub-optimal is because, I lose all the development tools on my local machine. Say I wanted
to use terraform to deploy/upgrade a service running on <code>Server B</code> server. I need to make sure terraform exists
on the intermediate server. Now, this is fine for something simple like terraform which is a single binary file
but may get more complicated for other pieces of software, especially if you cannot install extra packages on
the intermediate server. Also, there are other advantages in using your local development environment: you have
all your shortcuts saved, perhaps you use a different shell zsh, fish vs bash on the server itself. For whatever
reason, it may be more convenient to access <code>Server B</code> directly from our local machine.</p>
<details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">🔐 Production Access</summary>
  
  Now depending on where you work and how your policies work it may not be possible or a good idea to access
your production servers from your local machine. This is just a simple example of one reason you may
want to use a SOCKS proxy. There may be many others, such as accessing your test environment instead of
production.
</details>

<h2 id="socks-proxy">SOCKS Proxy</h2>
<p>In this section, I will show you how to solve the problem we described above. To solve this problem we will need to use,
a SOCKS (🧦 not this kinda socks) proxy. SOCKS is a layer 5 (on the OSI model, shown below) protocol. The protocol will allow
us to proxy to <code>Server A</code> and this server will then act as almost a middleman between the <code>Local Machine</code> and <code>Server B</code>.
The SOCKS proxy doesn&rsquo;t interpret any network traffic between the client (<code>Local Machine</code>) and the
server (<code>Server B</code>), it merely passes it onto between the two.</p>
<p>
  <img
    loading="lazy"
    src="https://upload.wikimedia.org/wikipedia/commons/2/2b/Osi-model.png"
    alt="OSI Model"
    
  /></p>
<details
  class="notice tip"
  open="true"
>
    <summary class="notice-title">SOCKS Proxy</summary>
  
  You can learn more about
<a href="https://securityintelligence.com/posts/socks-proxy-primer-what-is-socks5-and-why-should-you-use-it/">SOCKS proxies here</a>.
This article goes into much more detail than I do!
</details>

<h3 id="ssh-command">SSH Command</h3>
<p>So finally let&rsquo;s get onto how we can create a SOCKS proxy. To do this we will create an SSH tunnel.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ssh -D <span class="m">8123</span> -f -C -q -N haseeb@10.10.10.10
</span></span></code></pre></div><ul>
<li><code>-D 8123</code>: Opens a SOCKS5 proxy on local port <code>8123</code></li>
<li><code>-f</code>: Requests SSH to go to the background itself before executing the command</li>
<li><code>-C</code>: Compresses data before sending it</li>
<li><code>-q</code>: Quiet mode doesn&rsquo;t show any output</li>
<li><code>-N</code>: Doesn&rsquo;t execute remote commands, useful for just port forward (protocol 2+)</li>
</ul>
<details
  class="notice tip"
  open="true"
>
    <summary class="notice-title">Multiple Proxies</summary>
  
  You can create multiple SOCKS proxies by running the SSH command binding to different local ports.
</details>

<p>If the command worked, you now have a SOCKS proxy. One common use case of a SOCKS proxy is for internet
browsing using very much the same logic described above. Maybe you can access a website at work which is
behind a firewall, such as an authentication server&rsquo;s GUI etc. You can read more about using a SOCKS
proxy, in your browser <a href="https://ma.ttias.be/socks-proxy-linux-ssh-bypass-content-filters/">here</a>.
The diagram gives us a visual of what we&rsquo;ve just done.</p>
<div class="mermaid">graph LR
    subgraph Local Machine
        A[TCP Port :8123]
        B[SSH Client]
    end

    subgraph Remote Server
        C[Server A]
    end

    B -->|Opens Port| A
    B -->|SSH Tunnel| C
</div>

<h2 id="proxychains">Proxychains</h2>
<p>Now that we have SOCKS proxy running on our local machine, how can we use it to connect to <code>Server B</code> and say
use terraform to deploy a new service? Well, that&rsquo;s where <code>proxychains</code> comes in, or rather more specifically
<code>proxychains-ng</code>. The latter being a version which still gets relatively frequent updates.
To install <code>proxychains</code> on an Ubuntu/Debian based distro you can do something like this:</p>
<h3 id="install">Install</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo apt install proxychains-ng
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">vim /etc/proxychains4.conf
</span></span><span class="line"><span class="cl"><span class="o">[</span>ProxyList<span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># add proxy here ...</span>
</span></span><span class="line"><span class="cl"><span class="c1"># meanwile</span>
</span></span><span class="line"><span class="cl"><span class="c1"># defaults set to &#34;tor&#34;</span>
</span></span><span class="line"><span class="cl">socks5 127.0.0.1 <span class="m">8123</span>
</span></span></code></pre></div><p>Edit the configuration file as shown above, <code>socks5 127.0.0.1 8123</code>. Adjust the port <code>8123</code> to whatever port you set above.
Now that <code>proxychains</code> is setup. This is what our setup now looks like:</p>
<div class="mermaid">graph LR
    subgraph Local Machine
      A[TCP Port :8123]
      B[SSH Client]
      D[Proxychains]
    end

    subgraph Remote Server
      C[Server A]
    end

    D -->|Connects to| A
    B -->|Opens Port| A
    B -->|SSH Tunnel| C
</div>

<h3 id="examples">Examples</h3>
<p>If <code>Server B</code> had an IP address of <code>10.10.10.11</code> we could do:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">proxychains ssh haseeb@10.10.10.11
</span></span></code></pre></div><p>This would allow us to connect directly using SSH. Or perhaps if you had a web service running on <code>Server B</code> and wanted to
check a <code>healthcheck</code> endpoint to see if your API was running correctly you might do:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">proxychains curl https://10.10.10.11/api/v1/healthcheck
</span></span></code></pre></div><p>Or if you wanted to use terraform to deploy something on <code>Server B</code>, you could do something like:</p>
<details
  class="notice tip"
  open="true"
>
    <summary class="notice-title">Terraform</summary>
  
  To get terraform to use our SOCKS proxy we need to export the <code>HTTP_PROXY</code> and <code>HTTPS_PROXY</code> variables.
</details>

<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">HTTP_PROXY</span><span class="o">=</span>socks5://127.0.0.1:8123
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">HTTPS_PROXY</span><span class="o">=</span>socks5://127.0.0.1:8123
</span></span><span class="line"><span class="cl">proxychains terraform plan
</span></span><span class="line"><span class="cl">proxychains terraform apply
</span></span></code></pre></div><details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">ProxyChains TCP</summary>
  
  proxychains will only proxy TCP connections from your <code>Local Machine</code>.
However, it can resolve DNS through the proxy as well.
</details>

<p>What is essentially going on here is that traffic is being sent from our <code>Local Machine</code> to <code>Server A</code> which can
connect to <code>Server B</code> and pass traffic to the server. This in effect makes it seem our <code>Local Machine</code> can connect
directly to <code>Server B</code>.</p>
<div class="mermaid">graph LR
    subgraph Local Machine
      A[TCP Port :8123]
      B[SSH Client]
      D[Proxychains]
    end

    subgraph Remote Server
      C[Server A]
      E[Server B]
    end

    subgraph Firewall
      E
    end

    D -->|Connects to| A
    B -->|Opens Port| A
    B -->|SSH Tunnel| C
    C -->|TCP Connection| E
</div>

<p>So overall we have something as described in the diagram above!</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li>Read more about <a href="https://securityintelligence.com/posts/socks-proxy-primer-what-is-socks5-and-why-should-you-use-it/">SOCKS Proxies here</a></li>
<li>Read more about how to setup <a href="https://ma.ttias.be/socks-proxy-linux-ssh-bypass-content-filters/">a SOCKS here</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to source your Gatsby posts from another repo</title>
      <link>https://haseebmajid.dev/posts/2020-09-18-how-to-source-your-gatsby-posts-from-another-repo/</link>
      <pubDate>Fri, 18 Sep 2020 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2020-09-18-how-to-source-your-gatsby-posts-from-another-repo/</guid>
      <description>&lt;p&gt;In this article, we will go over how you can manage your markdown blog posts from another git repository (repo). Separate to the git repository for your Gatsby site.
This is the same process that I use to manage &lt;a href=&#34;https://gitlab.com/hmajid2301/articles&#34;&gt;this repo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So what this entails is the source code for my Gatsby site is in a repo called &lt;code&gt;portfolio-site&lt;/code&gt; on Gitlab.
Then I have another repo for all of my blog posts (in markdown) called &lt;code&gt;articles&lt;/code&gt;. During build time of the
Gatsby blog, we will import the markdown files from our &lt;code&gt;articles&lt;/code&gt; git repo and use it as a source of data for
our Gatsby blog.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this article, we will go over how you can manage your markdown blog posts from another git repository (repo). Separate to the git repository for your Gatsby site.
This is the same process that I use to manage <a href="https://gitlab.com/hmajid2301/articles">this repo</a>.</p>
<p>So what this entails is the source code for my Gatsby site is in a repo called <code>portfolio-site</code> on Gitlab.
Then I have another repo for all of my blog posts (in markdown) called <code>articles</code>. During build time of the
Gatsby blog, we will import the markdown files from our <code>articles</code> git repo and use it as a source of data for
our Gatsby blog.</p>
<h2 id="git-plugin">Git Plugin</h2>
<p>First, install the <a href="https://www.gatsbyjs.com/plugins/gatsby-source-git/?=git">Gatsby git plugin</a>, so that we can source our data from git.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">yarn add gatsby-source-git
</span></span></code></pre></div><p>Then add the plugin to your <code>gatsby-config.js</code> to tell it where to source its data from.</p>
<details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">Gatsby Filesystem</summary>
  
  You need to use the <code>gatsby-source-filesystem</code> before the <code>gatsby-source-git</code>.
You can read more about it <a href="https://github.com/stevetweeddale/gatsby-source-git/issues/22">here at this Github issue</a>.
</details>

<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">resolve</span><span class="o">:</span> <span class="sb">`gatsby-source-git`</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">options</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">name</span><span class="o">:</span> <span class="sb">`Articles`</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">remote</span><span class="o">:</span> <span class="s2">&#34;https://gitlab.com/hmajid2301/articles.git&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">branch</span><span class="o">:</span> <span class="sb">`master`</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">patterns</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;**/*&#34;</span><span class="p">,</span> <span class="s2">&#34;!**/*/index.md&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">},</span>
</span></span></code></pre></div><p>In our example, I will use <a href="https://gitlab.com/hmajid2301/articles">this</a>, the same repo this blog post originates from.
You can specify the branch to use if you want. The most interesting bit is the <code>patterns</code> section. This is where you can
specify which files to include and which to ignore <code>[&quot;**/*&quot;, &quot;!**/*/index.md&quot;]</code>. In this example, I want to ignore
all files called <code>index.md</code> because these are the ones that I use in my example Gatsby blogs in this repo for.
You can read more about the <a href="https://github.com/mrmlnc/fast-glob">pattern here</a>.</p>
<h2 id="graphql">GraphQL</h2>
<p>We can now check if the articles are being imported correctly by using the GraphQL IDE that comes with Gatsby.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">yarn develop
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Go to localhost:8000/__graphql</span>
</span></span></code></pre></div><p>Then run the following query.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-graphql" data-lang="graphql"><span class="line"><span class="cl"><span class="kd">query</span><span class="w"> </span><span class="nc">MyQuery</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="py">allMarkdownRemark</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="py">edges</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="py">node</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="py">fileAbsolutePath</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>You should see output like this, where it will list all of your blog posts/markdown files. Here you can verify your git
repo is being sourced correctly.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;data&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;allMarkdownRemark&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;edges&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nt">&#34;node&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;fileAbsolutePath&#34;</span><span class="p">:</span> <span class="s2">&#34;/home/haseeb/projects/personal/articles/35. Gatsby source git/source_code/.cache/gatsby-source-git/Articles/1. Expo with VirtualBox and Genymotion/README.md&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nt">&#34;node&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;fileAbsolutePath&#34;</span><span class="p">:</span> <span class="s2">&#34;/home/haseeb/projects/personal/articles/35. Gatsby source git/source_code/.cache/gatsby-source-git/Articles/11. React Navigation with React Native/README.md&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nt">&#34;node&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;fileAbsolutePath&#34;</span><span class="p">:</span> <span class="s2">&#34;/home/haseeb/projects/personal/articles/35. Gatsby source git/source_code/.cache/gatsby-source-git/Articles/13. REST API using OpenAPI, Flask &amp; Connexions/README.md&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>      <span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h2 id="gatsby-node">Gatsby Node</h2>
<p>Now make sure you have logic in your <code>gatsby-node.js</code> file to create a blog post page for every
markdown file that we source, i.e. one blog post for every item in the list above.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">exports</span><span class="p">.</span><span class="nx">createPages</span> <span class="o">=</span> <span class="kr">async</span> <span class="p">({</span> <span class="nx">graphql</span><span class="p">,</span> <span class="nx">actions</span> <span class="p">})</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="p">{</span> <span class="nx">createPage</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">actions</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">blogPost</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="sb">`./src/templates/blog-post.js`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">graphql</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="sb">`
</span></span></span><span class="line"><span class="cl"><span class="sb">      {
</span></span></span><span class="line"><span class="cl"><span class="sb">        allMarkdownRemark(
</span></span></span><span class="line"><span class="cl"><span class="sb">          sort: { fields: [frontmatter___date], order: DESC }
</span></span></span><span class="line"><span class="cl"><span class="sb">          limit: 1000
</span></span></span><span class="line"><span class="cl"><span class="sb">        ) {
</span></span></span><span class="line"><span class="cl"><span class="sb">          edges {
</span></span></span><span class="line"><span class="cl"><span class="sb">            node {
</span></span></span><span class="line"><span class="cl"><span class="sb">              frontmatter {
</span></span></span><span class="line"><span class="cl"><span class="sb">                title
</span></span></span><span class="line"><span class="cl"><span class="sb">                slug
</span></span></span><span class="line"><span class="cl"><span class="sb">              }
</span></span></span><span class="line"><span class="cl"><span class="sb">            }
</span></span></span><span class="line"><span class="cl"><span class="sb">          }
</span></span></span><span class="line"><span class="cl"><span class="sb">        }
</span></span></span><span class="line"><span class="cl"><span class="sb">      }
</span></span></span><span class="line"><span class="cl"><span class="sb">    `</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">result</span><span class="p">.</span><span class="nx">errors</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">throw</span> <span class="nx">result</span><span class="p">.</span><span class="nx">errors</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Create blog posts pages.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="kr">const</span> <span class="nx">posts</span> <span class="o">=</span> <span class="nx">result</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">allMarkdownRemark</span><span class="p">.</span><span class="nx">edges</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">posts</span><span class="p">.</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">post</span><span class="p">,</span> <span class="nx">index</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">previous</span> <span class="o">=</span> <span class="nx">index</span> <span class="o">===</span> <span class="nx">posts</span><span class="p">.</span><span class="nx">length</span> <span class="o">-</span> <span class="mi">1</span> <span class="o">?</span> <span class="kc">null</span> <span class="o">:</span> <span class="nx">posts</span><span class="p">[</span><span class="nx">index</span> <span class="o">+</span> <span class="mi">1</span><span class="p">].</span><span class="nx">node</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">next</span> <span class="o">=</span> <span class="nx">index</span> <span class="o">===</span> <span class="mi">0</span> <span class="o">?</span> <span class="kc">null</span> <span class="o">:</span> <span class="nx">posts</span><span class="p">[</span><span class="nx">index</span> <span class="o">-</span> <span class="mi">1</span><span class="p">].</span><span class="nx">node</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nx">createPage</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">      <span class="nx">path</span><span class="o">:</span> <span class="nx">post</span><span class="p">.</span><span class="nx">node</span><span class="p">.</span><span class="nx">frontmatter</span><span class="p">.</span><span class="nx">slug</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">component</span><span class="o">:</span> <span class="nx">blogPost</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">context</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">slug</span><span class="o">:</span> <span class="nx">post</span><span class="p">.</span><span class="nx">node</span><span class="p">.</span><span class="nx">frontmatter</span><span class="p">.</span><span class="nx">slug</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">previous</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">next</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><h2 id="gitlab-ci">Gitlab CI</h2>
<p>So every time we make a change in our article repo we want to trigger a rebuild of our site. Since I use Gitlab, I will
show you how you can do this with Gitlab CI. Every commit on the master branch, on our repo that contains our
articles, will trigger a rebuild on the Gatsby repo.</p>
<details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">Assumption</summary>
  
  This next section assumes that you use Gitlab to host your repos.
It also assumes that for your Gatsby blog you use Gitlab CI to build/publish it.
</details>

<p>For example, in my use case <a href="https://gitlab.com/hmajid2301/articles">the article repo</a> will trigger a rebuild for
<a href="https://gitlab.com/hmajid2301/portfolio-site/-/tree/7258fe7ca1366024f17da5952077cdc00f00a3a8">the Gatsby repo</a>.</p>
<p>First, go to your Gatsby repo then go to <code>Settings &gt; CI/CD &gt; Pipeline triggers</code>. Then create a new pipeline trigger,
save the newly created token to your CI/CD variables.</p>
<p><div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden">
  <iframe src="https://yewtu.be/embed/JbAk6xpBRxc"
    style="position:absolute;top:0;left:0;width:100%;height:100%;border:0" allowfullscreen></iframe>
</div>

<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden">
  <iframe src="https://yewtu.be/embed/X9m8UxmZgy8"
    style="position:absolute;top:0;left:0;width:100%;height:100%;border:0" allowfullscreen></iframe>
</div>
</p>
<p>Then also copy the <code>cURL</code> command shown and add the following to your <code>.gitlab-ci.yml</code>, with the <code>cURL</code> command.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">build</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">rebuild:portfolio-site</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">build</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">curlimages/curl</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">master</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">    </span>- <span class="s2">&#34;curl -X POST -F token=${TRIGGER_TOKEN} -F ref=master https://gitlab.com/api/v4/projects/19260161/trigger/pipeline&#34;</span></span></span></code></pre></div>
<p>Make sure you replace <code>19260161</code> with the project ID of your Gatsby blog, as this is the repo we want to trigger a
rebuild of. This means every time we push a new commit (i.e. an article) to the master branch of the articles,
it will trigger the pipeline to run on our Gatsby blog.</p>
<p>This will mean when it runs <code>yarn build</code> or <code>gatsby build</code> it&rsquo;ll source the markdown data from the latest commit on
our article git repo and will have the new article or whatever changes were made. The <code>.gitlab-ci</code> for our
Gatsby blog may look something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">node:12.13.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">cache</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">${CI_COMMIT_REF_SLUG}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">node_modules</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">build</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">deploy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">before_script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">yarn install</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">build:site</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">build</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">master</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn run build</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">artifacts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">public</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">deploy:site</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">deploy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">master</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">    </span>- <span class="l">npm i -g netlify-cli</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">    </span>- <span class="l">yarn deploy --site $NETLIFY_SITE_ID --auth $NETLIFY_PERSONAL_TOKEN --message &#34;$CI_COMMIT_TITLE&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">dependencies</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">build:site</span></span></span></code></pre></div>
<details
  class="notice tip"
  open="true"
>
    <summary class="notice-title">Artifcats</summary>
  
  The <code>deploy:site</code> job uses the build artifacts from the previous <code>build:site</code> job which has the site data stored in the <code>public</code>
folder. Due to the sites default settings on Netlify, this is what is uploaded when we use <code>netlify-cli</code>.
</details>

<p>I build and deploy the site from Gitlab CI to save the build minutes on Netlify. All you need to do this is to get your
<code>NETLIFY_SITE_ID</code> and create a <code>NETLIFY_PERSONAL_TOKEN</code> that can make the API request to publish the site on your behalf.</p>
<p><img
        loading="lazy"
        src="/posts/2020-09-18-how-to-source-your-gatsby-posts-from-another-repo/images/gatsby-blog-ci.png"
        type=""
        alt="Gatsby Blog CI"
        
      /></p>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">Gitlab CI</summary>
  
  You can, of course, change the <code>deploy:site</code> job to suit how you want to deploy your site, i.e. Gitlab pages, Github Pages, Vercel etc.
</details>

<h2 id="netlify">Netlify</h2>
<p>If you don&rsquo;t want to use Gitlab CI to build and publish your Gatsby blog and want to force a rebuild on Netlify.
Then you can do the following; Every time we push a new commit to the master branch on the article repo. We can
use a webhook to trigger a rebuild of our site on Netlify. To do this select your website on the Netlify GUI.
Then <code>Settings</code> &gt; <code>Build &amp; deploy</code> &gt; <code>Build hooks</code>. Add a new build hook. Then copy the <code>cURL</code> command,
so your article repo <code>.gitlab-ci.yml</code> now looks something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">build</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">rebuild:portfolio-site</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">build</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">curlimages/curl</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">master</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">    </span>- <span class="l">curl -X POST -d {} https://api.netlify.com/build_hooks/5f5e9c4f495aebe573c39aef</span></span></span></code></pre></div>
<p>You will want to turn <code>5f5e9c4f495aebe573c39aef</code> into a CI/CD variable, else anyone can force a rebuild of your site.</p>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden">
  <iframe src="https://yewtu.be/embed/7KRihyulbTQ"
    style="position:absolute;top:0;left:0;width:100%;height:100%;border:0" allowfullscreen></iframe>
</div>

<p>That&rsquo;s it, we learnt how we can manage our markdown articles in a separate repo to our Gatsby blog! We went over how we
can also automate the rebuild of our site using Gitlab CI and Netlify.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2020-09-18-how-to-source-your-gatsby-posts-from-another-repo/source_code">Example source code</a></li>
<li>Example <a href="https://gitlab.com/hmajid2301/portfolio-site/-/tree/7258fe7ca1366024f17da5952077cdc00f00a3a8">Gatsby blog</a> repo</li>
<li>Example <a href="https://gitlab.com/hmajid2301/articles">Articles</a> repo</li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Add an &#39;edit post&#39; button to your Gatsby blog</title>
      <link>https://haseebmajid.dev/posts/2020-09-07-add-an-edit-post-button-to-your-gatsby-blog/</link>
      <pubDate>Mon, 07 Sep 2020 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2020-09-07-add-an-edit-post-button-to-your-gatsby-blog/</guid>
      <description>&lt;p&gt;In this article, we will look at how we can add an &amp;ldquo;edit post&amp;rdquo; button, to your Gatsby blog. When this button is clicked it will take the user to your markdown file, on github/gitlab that was used to generate the blog post they are currently viewing.&lt;/p&gt;
&lt;div style=&#34;position:relative;padding-bottom:56.25%;height:0;overflow:hidden&#34;&gt;
  &lt;iframe src=&#34;https://yewtu.be/embed/rALo_BzGKs8&#34;
    style=&#34;position:absolute;top:0;left:0;width:100%;height:100%;border:0&#34; allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h2 id=&#34;setup&#34;&gt;Setup&lt;/h2&gt;
&lt;p&gt;Before we add the edit button to a Gatsby blog, let&amp;rsquo;s set up a simple Gatsby site using the &lt;code&gt;Gatsby blog starter&lt;/code&gt;.
You can skip this step and add the button to an existing site.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this article, we will look at how we can add an &ldquo;edit post&rdquo; button, to your Gatsby blog. When this button is clicked it will take the user to your markdown file, on github/gitlab that was used to generate the blog post they are currently viewing.</p>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden">
  <iframe src="https://yewtu.be/embed/rALo_BzGKs8"
    style="position:absolute;top:0;left:0;width:100%;height:100%;border:0" allowfullscreen></iframe>
</div>

<h2 id="setup">Setup</h2>
<p>Before we add the edit button to a Gatsby blog, let&rsquo;s set up a simple Gatsby site using the <code>Gatsby blog starter</code>.
You can skip this step and add the button to an existing site.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">npm -g install gatsby-cli
</span></span><span class="line"><span class="cl">gatsby new my-blog-starter https://github.com/gatsbyjs/gatsby-starter-blog
</span></span></code></pre></div><p>If you don&rsquo;t use the start above, you will need to make sure you have the <code>gatsby-source-filesystem</code> plugin installed. To import our markdown files. Your <code>gatsby-config.js</code> looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">resolve</span><span class="o">:</span> <span class="sb">`gatsby-source-filesystem`</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">options</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">path</span><span class="o">:</span> <span class="sb">`</span><span class="si">${</span><span class="nx">__dirname</span><span class="si">}</span><span class="sb">/content/blog`</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">name</span><span class="o">:</span> <span class="sb">`blog`</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span></code></pre></div><p>Then make sure you also have the <code>gatsby-transformer-remark</code> plugin installed
and it should be in your <code>gatsby-config.js</code> like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">resolve</span><span class="o">:</span> <span class="sb">`gatsby-transformer-remark`</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">options</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span></code></pre></div><h2 id="optional-blog-post">(Optional) Blog Post</h2>
<p>Let&rsquo;s assume our <code>gatsby-node.js</code> file looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">exports</span><span class="p">.</span><span class="nx">createPages</span> <span class="o">=</span> <span class="kr">async</span> <span class="p">({</span> <span class="nx">graphql</span><span class="p">,</span> <span class="nx">actions</span> <span class="p">})</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="p">{</span> <span class="nx">createPage</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">actions</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">blogPost</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="sb">`./src/templates/blog-post.js`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">graphql</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="sb">`
</span></span></span><span class="line"><span class="cl"><span class="sb">      {
</span></span></span><span class="line"><span class="cl"><span class="sb">        allMarkdownRemark(
</span></span></span><span class="line"><span class="cl"><span class="sb">          sort: { fields: [frontmatter___date], order: DESC }
</span></span></span><span class="line"><span class="cl"><span class="sb">          limit: 1000
</span></span></span><span class="line"><span class="cl"><span class="sb">        ) {
</span></span></span><span class="line"><span class="cl"><span class="sb">          edges {
</span></span></span><span class="line"><span class="cl"><span class="sb">            node {
</span></span></span><span class="line"><span class="cl"><span class="sb">              fields {
</span></span></span><span class="line"><span class="cl"><span class="sb">                slug
</span></span></span><span class="line"><span class="cl"><span class="sb">              }
</span></span></span><span class="line"><span class="cl"><span class="sb">              frontmatter {
</span></span></span><span class="line"><span class="cl"><span class="sb">                title
</span></span></span><span class="line"><span class="cl"><span class="sb">              }
</span></span></span><span class="line"><span class="cl"><span class="sb">            }
</span></span></span><span class="line"><span class="cl"><span class="sb">          }
</span></span></span><span class="line"><span class="cl"><span class="sb">        }
</span></span></span><span class="line"><span class="cl"><span class="sb">      }
</span></span></span><span class="line"><span class="cl"><span class="sb">    `</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">result</span><span class="p">.</span><span class="nx">errors</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">throw</span> <span class="nx">result</span><span class="p">.</span><span class="nx">errors</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Create blog posts pages.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="kr">const</span> <span class="nx">posts</span> <span class="o">=</span> <span class="nx">result</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">allMarkdownRemark</span><span class="p">.</span><span class="nx">edges</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">posts</span><span class="p">.</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">post</span><span class="p">,</span> <span class="nx">index</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">previous</span> <span class="o">=</span> <span class="nx">index</span> <span class="o">===</span> <span class="nx">posts</span><span class="p">.</span><span class="nx">length</span> <span class="o">-</span> <span class="mi">1</span> <span class="o">?</span> <span class="kc">null</span> <span class="o">:</span> <span class="nx">posts</span><span class="p">[</span><span class="nx">index</span> <span class="o">+</span> <span class="mi">1</span><span class="p">].</span><span class="nx">node</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">next</span> <span class="o">=</span> <span class="nx">index</span> <span class="o">===</span> <span class="mi">0</span> <span class="o">?</span> <span class="kc">null</span> <span class="o">:</span> <span class="nx">posts</span><span class="p">[</span><span class="nx">index</span> <span class="o">-</span> <span class="mi">1</span><span class="p">].</span><span class="nx">node</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nx">createPage</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">      <span class="nx">path</span><span class="o">:</span> <span class="nx">post</span><span class="p">.</span><span class="nx">node</span><span class="p">.</span><span class="nx">fields</span><span class="p">.</span><span class="nx">slug</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">component</span><span class="o">:</span> <span class="nx">blogPost</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">context</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">slug</span><span class="o">:</span> <span class="nx">post</span><span class="p">.</span><span class="nx">node</span><span class="p">.</span><span class="nx">fields</span><span class="p">.</span><span class="nx">slug</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">previous</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">next</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>This is how we create a new blog post for each of our markdown files. You can read more about how
markdown works with <a href="https://www.gatsbyjs.com/docs/adding-markdown-pages/">Gatsby here</a>.</p>
<p>Also let&rsquo;s use a simple template file for your blogs posts. So our <code>blog-post.js</code> looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span> <span class="nx">from</span> <span class="s2">&#34;react&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">Link</span><span class="p">,</span> <span class="nx">graphql</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;gatsby&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">BlogPostTemplate</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">data</span><span class="p">,</span> <span class="nx">pageContext</span><span class="p">,</span> <span class="nx">location</span> <span class="p">})</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">post</span> <span class="o">=</span> <span class="nx">data</span><span class="p">.</span><span class="nx">markdownRemark</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">siteTitle</span> <span class="o">=</span> <span class="nx">data</span><span class="p">.</span><span class="nx">site</span><span class="p">.</span><span class="nx">siteMetadata</span><span class="p">.</span><span class="nx">title</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="p">{</span> <span class="nx">previous</span><span class="p">,</span> <span class="nx">next</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">pageContext</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">Layout</span> <span class="na">location</span><span class="o">=</span><span class="p">{</span><span class="nx">location</span><span class="p">}</span> <span class="na">title</span><span class="o">=</span><span class="p">{</span><span class="nx">siteTitle</span><span class="p">}&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">SEO</span>
</span></span><span class="line"><span class="cl">        <span class="na">title</span><span class="o">=</span><span class="p">{</span><span class="nx">post</span><span class="p">.</span><span class="nx">frontmatter</span><span class="p">.</span><span class="nx">title</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="na">description</span><span class="o">=</span><span class="p">{</span><span class="nx">post</span><span class="p">.</span><span class="nx">frontmatter</span><span class="p">.</span><span class="nx">description</span> <span class="o">||</span> <span class="nx">post</span><span class="p">.</span><span class="nx">excerpt</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="p">&lt;/</span><span class="nt">Layout</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">BlogPostTemplate</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="nx">pageQuery</span> <span class="o">=</span> <span class="nx">graphql</span><span class="sb">`
</span></span></span><span class="line"><span class="cl"><span class="sb">  query BlogPostBySlug($slug: String!) {
</span></span></span><span class="line"><span class="cl"><span class="sb">    site {
</span></span></span><span class="line"><span class="cl"><span class="sb">      siteMetadata {
</span></span></span><span class="line"><span class="cl"><span class="sb">        title
</span></span></span><span class="line"><span class="cl"><span class="sb">      }
</span></span></span><span class="line"><span class="cl"><span class="sb">    }
</span></span></span><span class="line"><span class="cl"><span class="sb">    markdownRemark(fields: { slug: { eq: $slug } }) {
</span></span></span><span class="line"><span class="cl"><span class="sb">      id
</span></span></span><span class="line"><span class="cl"><span class="sb">      excerpt(pruneLength: 160)
</span></span></span><span class="line"><span class="cl"><span class="sb">      html
</span></span></span><span class="line"><span class="cl"><span class="sb">      frontmatter {
</span></span></span><span class="line"><span class="cl"><span class="sb">        title
</span></span></span><span class="line"><span class="cl"><span class="sb">        date(formatString: &#34;MMMM DD, YYYY&#34;)
</span></span></span><span class="line"><span class="cl"><span class="sb">        description
</span></span></span><span class="line"><span class="cl"><span class="sb">      }
</span></span></span><span class="line"><span class="cl"><span class="sb">    }
</span></span></span><span class="line"><span class="cl"><span class="sb">  }
</span></span></span><span class="line"><span class="cl"><span class="sb">`</span><span class="p">;</span>
</span></span></code></pre></div><h2 id="edit-button">Edit Button</h2>
<p>Ok, now we need two pieces of information the location of our project on git where our
markdown files are stored. In this example, it&rsquo;s here <code>https://gitlab.com/hmajid2301/articles</code>. We also need the path to the markdown file in the git repo. So we can combine
these two pieces of information together to get a URL to the markdown file on git.</p>
<p>First, we need a way to get the file path of the markdown file, we can do this with using our GraphQL query.
The same query we use to get other information such as title and contents. All we need to add is <code>fileAbsolutePath</code>
to the <code>markdownRemark</code> part of our query. This will return, as the name suggests, the absolute path to the file,
i.e. <code>/home/haseeb/projects/personal/articles/34. Gatsby edit button/source_code/content/blog/hello-world/index.md</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="nx">pageQuery</span> <span class="o">=</span> <span class="nx">graphql</span><span class="sb">`
</span></span></span><span class="line"><span class="cl"><span class="sb">  query BlogPostBySlug($slug: String!) {
</span></span></span><span class="line"><span class="cl"><span class="sb">    site {
</span></span></span><span class="line"><span class="cl"><span class="sb">      siteMetadata {
</span></span></span><span class="line"><span class="cl"><span class="sb">        title
</span></span></span><span class="line"><span class="cl"><span class="sb">      }
</span></span></span><span class="line"><span class="cl"><span class="sb">    }
</span></span></span><span class="line"><span class="cl"><span class="sb">    markdownRemark(fields: { slug: { eq: $slug } }) {
</span></span></span><span class="line"><span class="cl"><span class="sb">      id
</span></span></span><span class="line"><span class="cl"><span class="sb">      excerpt(pruneLength: 160)
</span></span></span><span class="line hl"><span class="cl"><span class="sb">      html
</span></span></span><span class="line"><span class="cl"><span class="sb">      fileAbsolutePath
</span></span></span><span class="line"><span class="cl"><span class="sb">      frontmatter {
</span></span></span><span class="line"><span class="cl"><span class="sb">        title
</span></span></span><span class="line"><span class="cl"><span class="sb">        date(formatString: &#34;MMMM DD, YYYY&#34;)
</span></span></span><span class="line"><span class="cl"><span class="sb">        description
</span></span></span><span class="line"><span class="cl"><span class="sb">      }
</span></span></span><span class="line"><span class="cl"><span class="sb">    }
</span></span></span><span class="line"><span class="cl"><span class="sb">  }
</span></span></span><span class="line"><span class="cl"><span class="sb">`</span><span class="p">;</span></span></span></code></pre></div>
<p>Now we need a way to use this file path to link to this page on Gitlab. Since I know that
<code>articles/</code> is a git repo, we want to remove <code>/home/haseeb/projects/personal/articles</code>
from <code>/home/haseeb/projects/personal/articles/34. Gatsby edit button/source_code/content/blog/hello-world/index.md</code>.</p>
<p>Then assuming the git URL of our repo, where the markdown files exists, is <code>https://gitlab.com/hmajid2301/articles</code>. The path to our markdown file on git could be something like
<code>https://gitlab.com/hmajid2301/articles/-/blob/master/34. Gatsby edit button/source_code/content/blog/hello-world/index.md</code>.</p>
<p>So let&rsquo;s add logic to our <code>blog-post.js</code> file to generate this git URL. After we have
updated our GraphQL query, we can add the some logic to our code to workout the git URL path.
Let&rsquo;s create a new function called <code>getGitMarkdownUrl()</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">BlogPostTemplate</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">data</span><span class="p">,</span> <span class="nx">pageContext</span><span class="p">,</span> <span class="nx">location</span> <span class="p">})</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">post</span> <span class="o">=</span> <span class="nx">data</span><span class="p">.</span><span class="nx">markdownRemark</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">siteTitle</span> <span class="o">=</span> <span class="nx">data</span><span class="p">.</span><span class="nx">site</span><span class="p">.</span><span class="nx">siteMetadata</span><span class="p">.</span><span class="nx">title</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="p">{</span> <span class="nx">previous</span><span class="p">,</span> <span class="nx">next</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">pageContext</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kd">function</span> <span class="nx">getGitMarkdownUrl</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">pathConst</span> <span class="o">=</span> <span class="s2">&#34;/articles/&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">gitURL</span> <span class="o">=</span> <span class="s2">&#34;https://gitlab.com/hmajid2301/articles&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">sliceIndex</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl">      <span class="nx">post</span><span class="p">.</span><span class="nx">fileAbsolutePath</span><span class="p">.</span><span class="nx">indexOf</span><span class="p">(</span><span class="nx">pathConst</span><span class="p">)</span> <span class="o">+</span> <span class="nx">pathConst</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">markdownFileGitPath</span> <span class="o">=</span> <span class="nx">post</span><span class="p">.</span><span class="nx">fileAbsolutePath</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="nx">sliceIndex</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">blogPostOnGit</span> <span class="o">=</span> <span class="sb">`</span><span class="si">${</span><span class="nx">gitURL</span><span class="si">}</span><span class="sb">/-/blob/master/</span><span class="si">${</span><span class="nx">markdownFileGitPath</span><span class="si">}</span><span class="sb">`</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">blogPostOnGit</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">gitMarkdownUrl</span> <span class="o">=</span> <span class="nx">getGitMarkdownUrl</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// ....
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">};</span>
</span></span></code></pre></div><blockquote>
<p>Warn: Don&rsquo;t forget to change the <code>gitURL</code> variable in your project!</p>
</blockquote>
<p>Where the following two lines remove everything before <code>/articles/</code>, so we get
<code>34. Gatsby edit button/source_code/content/blog/hello-world/index.md</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">sliceIndex</span> <span class="o">=</span> <span class="nx">post</span><span class="p">.</span><span class="nx">fileAbsolutePath</span><span class="p">.</span><span class="nx">indexOf</span><span class="p">(</span><span class="nx">pathConst</span><span class="p">)</span> <span class="o">+</span> <span class="nx">pathConst</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">markdownFileGitPath</span> <span class="o">=</span> <span class="nx">post</span><span class="p">.</span><span class="nx">fileAbsolutePath</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="nx">sliceIndex</span><span class="p">);</span>
</span></span></code></pre></div><p>Then we combine this with our git URL to end up with the path to the markdown file <code>https://gitlab.com/hmajid2301/articles/-/blob/master/34. Gatsby edit button/source_code/content/blog/hello-world/index.md</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">blogPostOnGit</span> <span class="o">=</span> <span class="sb">`</span><span class="si">${</span><span class="nx">gitURL</span><span class="si">}</span><span class="sb">/-/blob/master/</span><span class="si">${</span><span class="nx">markdownFileGitPath</span><span class="si">}</span><span class="sb">`</span><span class="p">;</span>
</span></span></code></pre></div><p>Finally, all we need to do is add the edit button and have it link to this <code>gitMarkdownUrl</code>. You can do something like
this below:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="p">{</span><span class="nx">gitMarkdownUrl</span><span class="p">}</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;noreferrer&#34;</span> <span class="na">target</span><span class="o">=</span><span class="s">&#34;_blank&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">EDIT</span> <span class="nx">THIS</span> <span class="nx">POST</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>If you want to make it look fancier, you can use <code>react-icons</code> to get a proper edit icon (as shown in the gif above).</p>
<p>That&rsquo;s it! That&rsquo;s all we needed to do when the user clicks on the edit button it&rsquo;ll take them to the git repo where
the markdown files exist. They can then perhaps fork the project make their edit and open a new merge or pull request
(GitLab vs GitHub) and add in the changes they want (if approved by you).</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2020-09-07-add-an-edit-post-button-to-your-gatsby-blog/source_code">Example source code</a></li>
<li><a href="https://haseebmajid.dev/">Site in video</a></li>
<li><a href="https://gitlab.com/hmajid2301/portfolio-site">Source code</a> for site in video</li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to auto create MRs in Gitlab</title>
      <link>https://haseebmajid.dev/posts/2020-08-31-how-to-auto-create-mrs-in-gitlab/</link>
      <pubDate>Mon, 31 Aug 2020 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2020-08-31-how-to-auto-create-mrs-in-gitlab/</guid>
      <description>&lt;p&gt;In this article, we will go over how we can use the &lt;code&gt;gitlab-auto-mr&lt;/code&gt; CLI script I wrote to help automate your Gitlab
workflow. This is a very simple script you can use with Gitlab which will auto-create merge requests (MRs) every time you
create a new branch on a project in Gitlab.&lt;/p&gt;
&lt;h2 id=&#34;optional-git-feature-branches&#34;&gt;(Optional) Git Feature Branches&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Feel free to skip this section if you are already familar with feature branch, skip to the &lt;code&gt;Gitlab Auto MR&lt;/code&gt; section&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this article, we will go over how we can use the <code>gitlab-auto-mr</code> CLI script I wrote to help automate your Gitlab
workflow. This is a very simple script you can use with Gitlab which will auto-create merge requests (MRs) every time you
create a new branch on a project in Gitlab.</p>
<h2 id="optional-git-feature-branches">(Optional) Git Feature Branches</h2>
<blockquote>
<p>Feel free to skip this section if you are already familar with feature branch, skip to the <code>Gitlab Auto MR</code> section</p>
</blockquote>
<p>Before I introduce what the script does and how we use it, let&rsquo;s go over why you might need to use it.
Say you&rsquo;re working on a project with multiple other people and you want to make sure you keep your master/production
branch clean. One way to do that is everyone uses &ldquo;feature&rdquo; branches. So for every feature being added to the project
you create a new short-lived branch off of the master branch. Then typically one developer will work on a feature
and make a merge request when ready to get the branch merged into the main master branch, to integrate their work
with everyone else&rsquo;s.</p>
<p>
  <img
    loading="lazy"
    src="https://docs.gitlab.com/ee/ci/introduction/img/gitlab_workflow_example_11_9.png"
    alt="Gitlab Workflow"
    
  /></p>
<p>This means your changes can get reviewed before they merged into the master branch. This helps keep the master branch
&ldquo;cleaner&rdquo;, it should have fewer bugs etc. One good way to visualise this is in the diagram above. Let&rsquo;s say we want
to add reaction buttons to a blog. We would create a new branch called something like <code>feature/add-reaction-buttons</code>.
Then we would commit our changes on the branch.</p>
<p>Usually, this is coupled with a CI pipeline which will auto-run against our code. It may run jobs like unit tests, linting
and static code analysis. This acts as a kind of first step review, we need to make sure it&rsquo;s passing (and is green) before
people even start to review our code, as seen in the above diagram. Once the CI pipeline is working the merge request can
be reviewed. After the merge request has been approved it can be merged into the master branch and we will add the new
feature to our codebase, which will eventually get deployed to production. We can also then delete our old branch.
You can read more about <a href="https://www.atlassian.com/git/tutorials/comparing-workflows/feature-branch-workflow">feature branches here</a>.</p>
<blockquote>
<p>Note there are many other git flows. This can project can be used in conjunction with all which will use some form of feature branching.</p>
</blockquote>
<h3 id="cicd">CI/CD</h3>
<blockquote>
<p>Feel free to skip this section if you are already familiar with CI/CD, Git and Gitlab CI.</p>
</blockquote>
<p>Continuous Integration (CI) is typically defined as making sure all code being integrated into codebase works.
It usually involves running a set of jobs referred to as a CI pipeline. Some jobs we may run include linting our
code and running unit tests. This is usually done automatically using a tool such as Travis, Circle or even Gitlab.</p>
<p>One use case for this is when others are adding new features to our codebase and we want to check it
still works. We can create a CI pipeline that will run unit tests against the new code automatically when a pull request
(GitHub) or merge request (Gitlab) is opened. This saves us a lot of time, rather than having to copy the new
features/code and then run the tests ourselves on our machine.</p>
<p>Continuous Delivery (CD) is typically an extension of CI to make sure that you can release new changes quickly.
This means automating your release process, such that you can deploy your application at any point of time just
by clicking on a button.</p>
<p>Continuous Deployment takes CD one step further by requiring no human intervention in deploying our application.
You can read more about <a href="https://www.atlassian.com/continuous-delivery/principles/continuous-integration-vs-delivery-vs-deployment">this here</a></p>
<h2 id="gitlab-auto-mr">Gitlab Auto MR</h2>
<p>I created a simple CLI script, that I run during on Gitlab CI, which will auto-create merge requests every time you create a new branch.
So you could have something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">pre</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">create:merge-request</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">registry.gitlab.com/gitlab-automation-toolkit/gitlab-auto-mr</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">pre</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">except</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">master</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">tags</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">gitlab_auto_mr -t master -c WIP -d .gitlab/merge_request_templates/merge_request.md -r -s --use-issue-name</span><span class="w">
</span></span></span></code></pre></div><p>To use this tool you will need to create a personal access token so that the tool can access the GitLab API on your behalf.
You can find out how to do that <a href="https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html">here</a>. In the
the example above I have set the private token in my CI/CD variables as <code>GITLAB_PRIVATE_TOKEN</code> but you can also pass in the
<code>--private-token</code> argument instead.</p>
<p><img
        loading="lazy"
        src="/posts/2020-08-31-how-to-auto-create-mrs-in-gitlab/images/ci-vars.gif"
        type=""
        alt="CI/CD Variables in Gitlab CI"
        
      /></p>
<p>If you use <code>registry.gitlab.com/gitlab-automation-toolkit/gitlab-auto-mr</code> this Docker image, the cli tool already
comes preinstalled with all of the dependencies as well. Else you can also install it manually using
<code>pip install gitlab-auto-mr</code>.</p>
<p>This particular job will only be run on a branch that is not called a master. This is because of the <code>except</code> clause
we defined above. Hence it should only run when we create a new feature branch. It will create a new merge request
if one does not already exist between this new branch and the master branch. The target branch it will create the
merge request to is set by the <code>-t</code> option, in this example <code>-t master</code>. For example, source branch -&gt; target branch.</p>
<p>Next, we append all of our new merge requests with WIP, set by the <code>-c WIP</code> argument, where WIP typically means
<code>Work in progress</code> so other devs know not to review our MR as it&rsquo;s not ready. Next, the tool also allows you to
specify a merge request template to use by passing the <code>-d</code> and giving it the path to a file. In this example
<code>.gitlab/merge_request_templates/merge_request.md</code> looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl"><span class="gh"># Description
</span></span></span><span class="line"><span class="cl"><span class="gh"></span>
</span></span><span class="line"><span class="cl"><span class="c">&lt;!-- please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. --&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">## Type
</span></span></span><span class="line"><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="cl"><span class="k">- [ ]</span> Bug Fix
</span></span><span class="line"><span class="cl"><span class="k">- [ ]</span> Improvement
</span></span><span class="line"><span class="cl"><span class="k">- [ ]</span> New Feature
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Fixes #<span class="c">&lt;!-- Issue Number --&gt;</span>
</span></span></code></pre></div><p>The other arguments do the following:</p>
<ul>
<li><code>-r</code>: Will remove the source branch (our feature branch) after the MR has been approved</li>
<li><code>-s</code>: Will squash our commits into a single commit, so each feature branch will appear as a single commit onto the master branch</li>
<li><code>--use-issue-name</code>: If set and you do something like <code>feature/#6</code> it will search for the issue with id <code>6</code> and pull information from there like labels and milestones etc. It will then assign those to this MR, an example of this can be seen with <a href="https://gitlab.com/hmajid2301/stegappasaurus/-/merge_requests/196">MR here</a> where the issue <a href="https://gitlab.com/hmajid2301/stegappasaurus/-/issues/211"><code>#211</code> is here</a></li>
</ul>
<p>You can get a full list of options available using this tool
<a href="https://gitlab.com/gitlab-automation-toolkit/gitlab-auto-mr">here at its project page</a>.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li>This project was originally inspired by <a href="https://gitlab.com/tmaier/gitlab-auto-merge-request">this other project</a> and this <a href="https://rpadovani.com/open-mr-gitlab-ci">post</a></li>
<li><a href="https://gitlab.com/gitlab-automation-toolkit/gitlab-auto-mr">Project Page</a></li>
<li><a href="https://gitlab.com/hmajid2301/stegappasaurus/-/blob/84d48e80d77a04870b748d2ac62e2cb698f17db8/.gitlab-ci.yml">An example project</a></li>
<li><a href="https://gitlab.com/hmajid2301/stegappasaurus/-/merge_requests/176">Example MRs created with this tool</a></li>
<li><a href="https://gitlab.com/hmajid2301/stegappasaurus/-/pipelines/120105476">Example pipeline running this tool</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to add offline search to a Gatsby blog</title>
      <link>https://haseebmajid.dev/posts/2020-08-20-how-to-add-offline-search-to-a-gatsby-blog/</link>
      <pubDate>Thu, 20 Aug 2020 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2020-08-20-how-to-add-offline-search-to-a-gatsby-blog/</guid>
      <description>&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2020-08-20-how-to-add-offline-search-to-a-gatsby-blog/images/main.gif&#34;
        type=&#34;&#34;
        alt=&#34;Search Gif&#34;
        
      /&gt;&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s take a look at how we can add offline local search 🔍 to a Gatsby blog. There are two main types of search we can
use an offline search like &lt;code&gt;elasticlunr&lt;/code&gt; and external API search engines like &lt;code&gt;ElasticSearch&lt;/code&gt;. These are typically more
scalable but also more expensive.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can find more info &lt;a href=&#34;https://www.gatsbyjs.com/docs/adding-search/#reach-skip-nav&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In this article, I will show you how to add offline search to your Gatsby blog using &lt;code&gt;elasticlunr&lt;/code&gt;. This means your
website needs to be indexed locally and will increase the bundle size as this index needs to be loaded by the client but
with the scale and size of personal blogs (100s, not 1000s of blog post) this shouldn&amp;rsquo;t make a massive difference.
We will also look at how we can add highlighting to our search results.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><img
        loading="lazy"
        src="/posts/2020-08-20-how-to-add-offline-search-to-a-gatsby-blog/images/main.gif"
        type=""
        alt="Search Gif"
        
      /></p>
<p>Let&rsquo;s take a look at how we can add offline local search 🔍 to a Gatsby blog. There are two main types of search we can
use an offline search like <code>elasticlunr</code> and external API search engines like <code>ElasticSearch</code>. These are typically more
scalable but also more expensive.</p>
<blockquote>
<p>You can find more info <a href="https://www.gatsbyjs.com/docs/adding-search/#reach-skip-nav">here</a>.</p>
</blockquote>
<p>In this article, I will show you how to add offline search to your Gatsby blog using <code>elasticlunr</code>. This means your
website needs to be indexed locally and will increase the bundle size as this index needs to be loaded by the client but
with the scale and size of personal blogs (100s, not 1000s of blog post) this shouldn&rsquo;t make a massive difference.
We will also look at how we can add highlighting to our search results.</p>
<blockquote>
<p>Note that you need to be careful with offline search because the entire search index has to be brought into the client, which can affect the bundle size significantly - GatsbyJS</p>
</blockquote>
<h2 id="setup">Setup</h2>
<p>Before we add search Gatsby blog, let&rsquo;s setup a simple Gatsby site using the <code>Gatsby blog starter</code>, you can of course
skip this step and add search to an existing site.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">npm -g install gatsby-cli
</span></span><span class="line"><span class="cl">gatsby new my-blog-starter https://github.com/gatsbyjs/gatsby-starter-blog
</span></span></code></pre></div><h2 id="markdown">Markdown</h2>
<p>The search component will use the data within our markdown and index it, so that the client can search with this
data later. In this example I will assume your markdown files look something like the example below:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl">---
</span></span><span class="line"><span class="cl">title: Hello World
</span></span><span class="line"><span class="cl">date: &#34;2015-05-01&#34;
</span></span><span class="line"><span class="cl">tags: [&#34;food&#34;, &#34;duck&#34;]
</span></span><span class="line"><span class="cl">---
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">This is my first post on my new fake blog! How exciting!
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">I&#39;m sure I&#39;ll write a lot more interesting things in the future.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">...
</span></span></code></pre></div><p>The top part of a markdown file between the <code>---</code> is known as the front matter, often we can access this data as a
key/value (like a Python dictionary).</p>
<blockquote>
<p>Note: In this example, we will be using the <a href="https://www.gatsbyjs.com/plugins/gatsby-transformer-remark/?=markdown"><code>MarkdownRemark</code></a>, but you can use search for anything just adjust the examples below as required.</p>
</blockquote>
<h2 id="search">Search</h2>
<p>Now onto adding search to our site.</p>
<h3 id="elasticlunr">Elasticlunr</h3>
<p>We will use <code>elasticlunr</code> for our offline/local search. Luckily there is a Gatsby plugin we can use, which makes
integrating it into our site very easy. First install the following plugin and the library:
<code>yarn add @gatsby-contrib/gatsby-plugin-elasticlunr-search elasticlunr</code>.</p>
<p>Then open your <code>gatsby-config.js</code> and add the following:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">resolve</span><span class="o">:</span> <span class="sb">`@gatsby-contrib/gatsby-plugin-elasticlunr-search`</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">options</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">fields</span><span class="o">:</span> <span class="p">[</span><span class="sb">`title`</span><span class="p">,</span> <span class="sb">`tags`</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">      <span class="nx">resolvers</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">MarkdownRemark</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nx">title</span><span class="o">:</span> <span class="p">(</span><span class="nx">node</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nx">node</span><span class="p">.</span><span class="nx">frontmatter</span><span class="p">.</span><span class="nx">title</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nx">tags</span><span class="o">:</span> <span class="p">(</span><span class="nx">node</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nx">node</span><span class="p">.</span><span class="nx">frontmatter</span><span class="p">.</span><span class="nx">tags</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nx">path</span><span class="o">:</span> <span class="p">(</span><span class="nx">node</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nx">node</span><span class="p">.</span><span class="nx">frontmatter</span><span class="p">.</span><span class="nx">slug</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span></code></pre></div><p>Here we are telling the search plugin what (GraphQL) fields to index. In this example, we want to index
the title and tags. We could also index the content if we wanted by adding the following line after path
<code>html: (node) =&gt; node.internal.content,</code> and adding <code>html</code> to the <code>fields</code> array. You can index
any field available in GraphQL, provided by the <code>MarkdownRemark</code> plugin (or whichever plugin you are using).</p>
<h4 id="graphql-optional">GraphQL (Optional)</h4>
<p>Slight aside here but if you wish to explore and take a look at the data available/provided by the <code>MarkdownRemark</code> plugin, you can start your
Gatsby site, typically using <code>yarn develop</code> and once the command has finished doing it&rsquo;s magic 🎉, visit this page
<code>http://localhost:8000/___graphql</code>. This provides us with our GraphQL playground (an IDE) and is a great way to understand what is going
on with our GraphQL queries if you don&rsquo;t understand.</p>
<p>For example, if you type the following into the main field and press the play button at the top.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-graphql" data-lang="graphql"><span class="line"><span class="cl"><span class="kd">query</span><span class="w"> </span><span class="nc">MyQuery</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="py">allMarkdownRemark</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="py">sort</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nc">order</span><span class="p">:</span><span class="w"> </span><span class="nc">DESC</span><span class="p">,</span><span class="w"> </span><span class="py">fields</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="nc">frontmatter___date</span><span class="p">]</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="py">filter</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nc">frontmatter</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nc">title</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nc">ne</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;Uses&#34;</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nc">edges</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="py">node</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="py">id</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="py">excerpt</span><span class="p">(</span><span class="py">pruneLength</span><span class="p">:</span><span class="w"> </span><span class="nc">100</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="py">frontmatter</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="py">date</span><span class="p">(</span><span class="py">formatString</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;YYYY-MM-DD&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nc">title</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="py">tags</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>You should see something like (in this example):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;data&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;allMarkdownRemark&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;edges&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nt">&#34;node&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="s2">&#34;1a7e02d4-620a-5268-8149-2d8cbf26a20a&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;excerpt&#34;</span><span class="p">:</span> <span class="s2">&#34;Far far away, behind the word mountains, far from the countries Vokalia and\nConsonantia, there live…&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;frontmatter&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">              <span class="nt">&#34;date&#34;</span><span class="p">:</span> <span class="s2">&#34;2015-05-28&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="nt">&#34;title&#34;</span><span class="p">:</span> <span class="s2">&#34;New Beginnings&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="nt">&#34;tags&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;deer&#34;</span><span class="p">,</span> <span class="s2">&#34;horse&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">          <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nt">&#34;node&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="s2">&#34;fe83f167-8f86-51fe-a981-c5189625e270&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;excerpt&#34;</span><span class="p">:</span> <span class="s2">&#34;Wow! I love blogging so much already. Did you know that “despite its name, salted duck eggs can also…&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;frontmatter&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">              <span class="nt">&#34;date&#34;</span><span class="p">:</span> <span class="s2">&#34;2015-05-06&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="nt">&#34;title&#34;</span><span class="p">:</span> <span class="s2">&#34;My Second Post!&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="nt">&#34;tags&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;food&#34;</span><span class="p">,</span> <span class="s2">&#34;blog&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">          <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nt">&#34;node&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="s2">&#34;4e865c18-e797-5da8-a46d-902949a00c7f&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;excerpt&#34;</span><span class="p">:</span> <span class="s2">&#34;This is my first post on my new fake blog! How exciting! I’m sure I’ll write a lot more interesting…&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;frontmatter&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">              <span class="nt">&#34;date&#34;</span><span class="p">:</span> <span class="s2">&#34;2015-05-01&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="nt">&#34;title&#34;</span><span class="p">:</span> <span class="s2">&#34;Hello World&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="nt">&#34;tags&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;food&#34;</span><span class="p">,</span> <span class="s2">&#34;duck&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">          <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;extensions&#34;</span><span class="p">:</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>As you can see this is a very familiar structure to the one we described in our search config above. If you play
around with the fields on the left-hand side of the IDE, you should be able to get a better understanding of all
fields you can index.</p>
<h2 id="logic">Logic</h2>
<p>Now we will add the relevant JSX components we need for search to our site.</p>
<h3 id="tailwindcss-optional">TailwindCSS (Optional)</h3>
<p>You can follow this <a href="https://www.gatsbyjs.com/docs/tailwind-css/">tutorial</a> to add TailwindCSS.
We will add TailwindCSS to this Gatsby project and we will use this to style our components.
First install the following dependencies:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">yarn add tailwindcss gatsby-plugin-postcss @emotion/core @emotion/styled gatsby-plugin-emotion
</span></span><span class="line"><span class="cl">yarn add -D twin.macro <span class="c1"># twin.macro allows us to use css-in-js a bit like emotion/styled-components except for tailwind</span>
</span></span><span class="line"><span class="cl">npx tailwindcss init
</span></span></code></pre></div><p>Then add the following to your <code>gatsby-config.js</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">plugins</span><span class="o">:</span> <span class="p">[</span><span class="sb">`gatsby-plugin-postcss`</span><span class="p">,</span> <span class="sb">`gatsby-plugin-emotion`</span><span class="p">],</span>
</span></span></code></pre></div><p>Then create a new file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">vim main.css
</span></span><span class="line"><span class="cl"><span class="c1">#...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Contents of the file</span>
</span></span><span class="line"><span class="cl">@tailwind base<span class="p">;</span>
</span></span><span class="line"><span class="cl">@tailwind components<span class="p">;</span>
</span></span><span class="line"><span class="cl">@tailwind utilities<span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ...</span>
</span></span></code></pre></div><p>Then add the following line to <code>gatsby-browser.js</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="s2">&#34;./src/main.css&#34;</span><span class="p">;</span>
</span></span></code></pre></div><p>Finally create a new file <code>postcss.config.js</code> and add the following:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nx">plugins</span><span class="o">:</span> <span class="p">[</span><span class="nx">require</span><span class="p">(</span><span class="s2">&#34;tailwindcss&#34;</span><span class="p">)],</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><h3 id="components">Components</h3>
<p>We will create all of the components in the following <code>src/components</code> folder.
First, let&rsquo;s create the <code>Input.jsx</code> component for the text input, which looks something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span> <span class="nx">from</span> <span class="s2">&#34;react&#34;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">tw</span> <span class="nx">from</span> <span class="s2">&#34;twin.macro&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">Input</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">forwardRef</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="p">({</span> <span class="nx">className</span><span class="p">,</span> <span class="nx">label</span><span class="p">,</span> <span class="nx">onChange</span><span class="p">,</span> <span class="nx">placeholder</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">,</span> <span class="nx">value</span> <span class="p">},</span> <span class="nx">ref</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">TextInput</span>
</span></span><span class="line"><span class="cl">      <span class="na">ref</span><span class="o">=</span><span class="p">{</span><span class="nx">ref</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="na">aria</span><span class="err">-</span><span class="na">label</span><span class="o">=</span><span class="p">{</span><span class="nx">label</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="na">className</span><span class="o">=</span><span class="p">{</span><span class="sb">`bg-background text-header placeholder-main </span><span class="si">${</span><span class="nx">className</span><span class="si">}</span><span class="sb">`</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="na">onChange</span><span class="o">=</span><span class="p">{</span><span class="nx">onChange</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="na">placeholder</span><span class="o">=</span><span class="p">{</span><span class="nx">placeholder</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="na">type</span><span class="o">=</span><span class="s">&#34;text&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="na">value</span><span class="o">=</span><span class="p">{</span><span class="nx">value</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">TextInput</span> <span class="o">=</span> <span class="nx">tw</span><span class="p">.</span><span class="nx">input</span><span class="sb">`inline px-2 h-full w-full text-left inline text-lg transition duration-300`</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">Input</span>
</span></span></code></pre></div><p>Since we are using <code>twin.macro</code> we can use syntax like <code>const TextInput = tw.input``</code>. Hence we can use the name <code>TextInput</code>.
in our component, where <code>TextInput</code> is just an input with some tailwindcss styles we&rsquo;ve defined.</p>
<p>Note we added a React forward ref so that, we can autofocus on this input later on.
So when the input is shown to the client we are already focused into the input.</p>
<p>Next, let&rsquo;s create a component for <code>SearchItem.jsx</code>. This is a single search item found.
In this case, we will only show the title and read more button. Note we are using the
<code>react-highlight-words</code> library to highlight words from the search query.</p>
<p>The prop <code>query</code> is the search query the user typed in. In the <code>Highlighter</code> component the <code>searchWords</code> prop
is given a list of words to highlight, hence we need to split the string into an array. For example, if we
had the search query <code>&quot;A blog post&quot;</code>, it would become <code>[&quot;A&quot;, &quot;blog&quot;, &quot;post&quot;]</code>, and will highlight either of
those words in the title (A, blog or post).</p>
<blockquote>
<p>Note: Again you can extend this to include perhaps a description of the blog post (first 160 characters) etc. We are just keeping it simple for this example.</p>
</blockquote>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">Link</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;gatsby&#34;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span> <span class="nx">from</span> <span class="s2">&#34;react&#34;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">Highlighter</span> <span class="nx">from</span> <span class="s2">&#34;react-highlight-words&#34;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">tw</span> <span class="nx">from</span> <span class="s2">&#34;twin.macro&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">SearchItem</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">path</span><span class="p">,</span> <span class="nx">title</span><span class="p">,</span> <span class="nx">query</span> <span class="p">})</span> <span class="p">=&gt;</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">SearchItemContainer</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">SearchTitle</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">Link</span>
</span></span><span class="line"><span class="cl">        <span class="na">className</span><span class="o">=</span><span class="s">&#34;hover:text-white hover:bg-blue-500 hover:p-1 rounded&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">to</span><span class="o">=</span><span class="p">{</span><span class="nx">path</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">Highlighter</span>
</span></span><span class="line"><span class="cl">          <span class="na">autoEscape</span>
</span></span><span class="line"><span class="cl">          <span class="na">highlightStyle</span><span class="o">=</span><span class="p">{{</span> <span class="nx">backgroundColor</span><span class="o">:</span> <span class="s2">&#34;#ffd54f&#34;</span> <span class="p">}}</span>
</span></span><span class="line"><span class="cl">          <span class="na">searchWords</span><span class="o">=</span><span class="p">{</span><span class="nx">query</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="s2">&#34; &#34;</span><span class="p">)}</span>
</span></span><span class="line"><span class="cl">          <span class="na">textToHighlight</span><span class="o">=</span><span class="p">{</span><span class="nx">title</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;/</span><span class="nt">Link</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">SearchTitle</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">ReadMore</span> <span class="na">className</span><span class="o">=</span><span class="s">&#34;hover:text-blue-500 text-lg py-2&#34;</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;button&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">Link</span> <span class="na">to</span><span class="o">=</span><span class="p">{</span><span class="nx">path</span><span class="p">}&gt;</span><span class="nx">Read</span> <span class="nx">More</span><span class="p">&lt;/</span><span class="nt">Link</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">ReadMore</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">SearchItemContainer</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">SearchItemContainer</span> <span class="o">=</span> <span class="nx">tw</span><span class="p">.</span><span class="nx">div</span><span class="sb">`my-10`</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">SearchTitle</span> <span class="o">=</span> <span class="nx">tw</span><span class="p">.</span><span class="nx">h2</span><span class="sb">`text-2xl font-semibold`</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">ReadMore</span> <span class="o">=</span> <span class="nx">tw</span><span class="p">.</span><span class="nx">button</span><span class="sb">`hover:text-blue-500 text-lg py-2`</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">SearchItem</span>
</span></span></code></pre></div><p>Next, we have a component we will call <code>SearchItems.jsx</code>, which will be a list of the search results and look
something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span> <span class="nx">from</span> <span class="s2">&#34;react&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">SearchItem</span> <span class="nx">from</span> <span class="s2">&#34;./SearchItem&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">SearchItems</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">results</span><span class="p">,</span> <span class="nx">query</span> <span class="p">})</span> <span class="p">=&gt;</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">ul</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="nx">results</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">page</span> <span class="p">=&gt;</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">li</span> <span class="na">key</span><span class="o">=</span><span class="p">{</span><span class="nx">page</span><span class="p">.</span><span class="nx">id</span><span class="p">}&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">SearchItem</span> <span class="na">path</span><span class="o">=</span><span class="p">{</span><span class="sb">`</span><span class="si">${</span><span class="nx">page</span><span class="p">.</span><span class="nx">path</span><span class="si">}</span><span class="sb">`</span><span class="p">}</span> <span class="na">query</span><span class="o">=</span><span class="p">{</span><span class="nx">query</span><span class="p">}</span> <span class="na">title</span><span class="o">=</span><span class="p">{</span><span class="nx">page</span><span class="p">.</span><span class="nx">title</span><span class="p">}</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;/</span><span class="nt">li</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">))}</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">ul</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">SearchItems</span>
</span></span></code></pre></div><p>Now onto the main component, the component that will actually work out the results to show
to the client. We will call this component <code>Search.jsx</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">Index</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;elasticlunr&#34;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span><span class="p">,</span> <span class="p">{</span> <span class="nx">useState</span><span class="p">,</span> <span class="nx">useEffect</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;react&#34;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">tw</span> <span class="nx">from</span> <span class="s2">&#34;twin.macro&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">Input</span> <span class="nx">from</span> <span class="s2">&#34;./Input&#34;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">SearchItems</span> <span class="nx">from</span> <span class="s2">&#34;./SearchItems&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">Search</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">searchIndex</span> <span class="p">})</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">index</span> <span class="o">=</span> <span class="nx">Index</span><span class="p">.</span><span class="nx">load</span><span class="p">(</span><span class="nx">searchIndex</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="p">[</span><span class="nx">query</span><span class="p">,</span> <span class="nx">setQuery</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="s2">&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="p">[</span><span class="nx">results</span><span class="p">,</span> <span class="nx">setResults</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">([])</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">searchInput</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">createRef</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">useEffect</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">searchResults</span><span class="p">(</span><span class="s2">&#34;blog&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nx">searchInput</span><span class="p">.</span><span class="nx">current</span><span class="p">.</span><span class="nx">focus</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span> <span class="p">[])</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kd">function</span> <span class="nx">searchResults</span><span class="p">(</span><span class="nx">searchQuery</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">res</span> <span class="o">=</span> <span class="nx">index</span><span class="p">.</span><span class="nx">search</span><span class="p">(</span><span class="nx">searchQuery</span><span class="p">,</span> <span class="p">{</span> <span class="nx">expand</span><span class="o">:</span> <span class="kc">true</span> <span class="p">}).</span><span class="nx">map</span><span class="p">(({</span> <span class="nx">ref</span> <span class="p">})</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="k">return</span> <span class="nx">index</span><span class="p">.</span><span class="nx">documentStore</span><span class="p">.</span><span class="nx">getDoc</span><span class="p">(</span><span class="nx">ref</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="nx">setResults</span><span class="p">(</span><span class="nx">res</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">SearchContainer</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">SearchInputContainer</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">Input</span>
</span></span><span class="line"><span class="cl">          <span class="na">ref</span><span class="o">=</span><span class="p">{</span><span class="nx">searchInput</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">          <span class="na">className</span><span class="o">=</span><span class="s">&#34;px-2&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="na">label</span><span class="o">=</span><span class="s">&#34;Search&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="na">onChange</span><span class="o">=</span><span class="p">{</span><span class="nx">event</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="kr">const</span> <span class="nx">searchQuery</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">value</span>
</span></span><span class="line"><span class="cl">            <span class="nx">setQuery</span><span class="p">(</span><span class="nx">searchQuery</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="nx">searchResults</span><span class="p">(</span><span class="nx">searchQuery</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">          <span class="p">}}</span>
</span></span><span class="line"><span class="cl">          <span class="na">placeholder</span><span class="o">=</span><span class="s">&#34;Search&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="na">value</span><span class="o">=</span><span class="p">{</span><span class="nx">query</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;/</span><span class="nt">SearchInputContainer</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">SearchItems</span> <span class="na">query</span><span class="o">=</span><span class="p">{</span><span class="nx">query</span><span class="p">}</span> <span class="na">results</span><span class="o">=</span><span class="p">{</span><span class="nx">results</span><span class="p">}</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">SearchContainer</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">SearchContainer</span> <span class="o">=</span> <span class="nx">tw</span><span class="p">.</span><span class="nx">div</span><span class="sb">`max-w-screen-md mx-auto pt-8`</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">SearchInputContainer</span> <span class="o">=</span> <span class="nx">tw</span><span class="p">.</span><span class="nx">div</span><span class="sb">`flex w-full text-left h-12 text-lg focus-within:shadow-outline my-8`</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">Search</span>
</span></span></code></pre></div><p>Let&rsquo;s break this down:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">index</span> <span class="o">=</span> <span class="nx">Index</span><span class="p">.</span><span class="nx">load</span><span class="p">(</span><span class="nx">searchIndex</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="p">[</span><span class="nx">query</span><span class="p">,</span> <span class="nx">setQuery</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="s2">&#34;&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="p">[</span><span class="nx">results</span><span class="p">,</span> <span class="nx">setResults</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">([]);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">searchInput</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">createRef</span><span class="p">();</span>
</span></span></code></pre></div><p>The first part will be used to store some variables we need later on. Like storing the current query the client has
typed into the search, the current search results and a reference to the search input so we can focus into it.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="nx">useEffect</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">searchResults</span><span class="p">(</span><span class="s2">&#34;blog&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nx">searchInput</span><span class="p">.</span><span class="nx">current</span><span class="p">.</span><span class="nx">focus</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">},</span> <span class="p">[]);</span>
</span></span></code></pre></div><p>Next, the <code>useEffect</code> hook is called as soon as the component mounts, so as soon as the component mounts we will focus
into the <code>searchInput</code> component <code>searchInput.current.focus()</code> and we pre-fill the search with any blog post with
<code>&quot;blog&quot;</code> in it&rsquo;s title/tags <code>searchResults(&quot;blog&quot;)</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">searchResults</span><span class="p">(</span><span class="nx">searchQuery</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">res</span> <span class="o">=</span> <span class="nx">index</span><span class="p">.</span><span class="nx">search</span><span class="p">(</span><span class="nx">searchQuery</span><span class="p">,</span> <span class="p">{</span> <span class="nx">expand</span><span class="o">:</span> <span class="kc">true</span> <span class="p">}).</span><span class="nx">map</span><span class="p">(({</span> <span class="nx">ref</span> <span class="p">})</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">index</span><span class="p">.</span><span class="nx">documentStore</span><span class="p">.</span><span class="nx">getDoc</span><span class="p">(</span><span class="nx">ref</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl">  <span class="nx">setResults</span><span class="p">(</span><span class="nx">res</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This is the actual function which gets our search results. It makes the query with <code>elasticlunr</code> and
stores the results in out state hook variable <code>result</code> using the set function <code>setResults(res)</code>. The first part
of the function does most of the heavy lifting returning a list of possible results to show to the client.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">Input</span>
</span></span><span class="line"><span class="cl">  <span class="na">ref</span><span class="o">=</span><span class="p">{</span><span class="nx">searchInput</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="na">className</span><span class="o">=</span><span class="s">&#34;px-2&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="na">label</span><span class="o">=</span><span class="s">&#34;Search&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="na">onChange</span><span class="o">=</span><span class="p">{(</span><span class="nx">e</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">searchQuery</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">setQuery</span><span class="p">(</span><span class="nx">searchQuery</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">searchResults</span><span class="p">(</span><span class="nx">searchQuery</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}}</span>
</span></span><span class="line"><span class="cl">  <span class="na">placeholder</span><span class="o">=</span><span class="s">&#34;Search&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="na">value</span><span class="o">=</span><span class="p">{</span><span class="nx">query</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">/&gt;</span>
</span></span></code></pre></div><blockquote>
<p>Note: <code>e.target.query</code> is the current value in the text input.</p>
</blockquote>
<p>Finally when taking a look at the input you can see the <code>ref={searchInput}</code> we defined above
being assigned here, so we can focus on this component. Next on any change i.e. a keypress we call the <code>onChange</code>
function. Where we update the query with the new search query <code>setQuery(searchQuery)</code> again using a state hook.
Then we call the <code>searchResults(searchQuery)</code> function which will update the results.</p>
<p>This is then shown to the client using our SearchItems component defined above like so:
<code>&lt;SearchItems query={query} results={results} /&gt;</code>.</p>
<p>Finally, we have a &ldquo;<code>SearchBar.tsx</code>&rdquo;, this is the component we will use to tie everything together.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">styled</span> <span class="nx">from</span> <span class="s2">&#34;@emotion/styled&#34;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">graphql</span><span class="p">,</span> <span class="nx">StaticQuery</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;gatsby&#34;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span><span class="p">,</span> <span class="p">{</span> <span class="nx">useState</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;react&#34;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">tw</span> <span class="nx">from</span> <span class="s2">&#34;twin.macro&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">Search</span> <span class="nx">from</span> <span class="s2">&#34;./Search&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">SearchBar</span> <span class="o">=</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="p">[</span><span class="nx">showSearch</span><span class="p">,</span> <span class="nx">setShowSearch</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="kc">false</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kd">function</span> <span class="nx">hideSearch</span><span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">placeholder</span> <span class="o">!==</span> <span class="s2">&#34;Search&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">setShowSearch</span><span class="p">(</span><span class="kc">false</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">SearchComponent</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">h1</span>
</span></span><span class="line"><span class="cl">        <span class="na">className</span><span class="o">=</span><span class="s">&#34;hover:cursor-pointer text-orange-800 text-2xl my-10&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">onClick</span><span class="o">=</span><span class="p">{()</span> <span class="p">=&gt;</span> <span class="nx">setShowSearch</span><span class="p">(</span><span class="o">!</span><span class="nx">showSearch</span><span class="p">)}</span>
</span></span><span class="line"><span class="cl">      <span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nx">Search</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;/</span><span class="nt">h1</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">SearchOverlay</span>
</span></span><span class="line"><span class="cl">        <span class="na">onClick</span><span class="o">=</span><span class="p">{</span><span class="nx">e</span> <span class="p">=&gt;</span> <span class="nx">hideSearch</span><span class="p">(</span><span class="nx">e</span><span class="p">)}</span>
</span></span><span class="line"><span class="cl">        <span class="na">onKeyPress</span><span class="o">=</span><span class="p">{</span><span class="nx">e</span> <span class="p">=&gt;</span> <span class="nx">hideSearch</span><span class="p">(</span><span class="nx">e</span><span class="p">)}</span>
</span></span><span class="line"><span class="cl">        <span class="na">role</span><span class="o">=</span><span class="s">&#34;presentation&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">showSearch</span><span class="o">=</span><span class="p">{</span><span class="nx">showSearch</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">StaticQuery</span>
</span></span><span class="line"><span class="cl">          <span class="na">query</span><span class="o">=</span><span class="p">{</span><span class="nx">graphql</span><span class="sb">`
</span></span></span><span class="line"><span class="cl"><span class="sb">            query SearchIndexQuery {
</span></span></span><span class="line"><span class="cl"><span class="sb">              siteSearchIndex {
</span></span></span><span class="line"><span class="cl"><span class="sb">                index
</span></span></span><span class="line"><span class="cl"><span class="sb">              }
</span></span></span><span class="line"><span class="cl"><span class="sb">            }
</span></span></span><span class="line"><span class="cl"><span class="sb">          `</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">          <span class="na">render</span><span class="o">=</span><span class="p">{</span><span class="nx">data</span> <span class="p">=&gt;</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">SearchContainer</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">              <span class="p">{</span><span class="nx">showSearch</span> <span class="o">&amp;&amp;</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="p">&lt;</span><span class="nt">Search</span> <span class="na">searchIndex</span><span class="o">=</span><span class="p">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">siteSearchIndex</span><span class="p">.</span><span class="nx">index</span><span class="p">}</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">              <span class="p">)}</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;/</span><span class="nt">SearchContainer</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">          <span class="p">)}</span>
</span></span><span class="line"><span class="cl">        <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;/</span><span class="nt">SearchOverlay</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">SearchComponent</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">SearchComponent</span> <span class="o">=</span> <span class="nx">tw</span><span class="p">.</span><span class="nx">div</span><span class="sb">`flex-grow flex`</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">SearchContainer</span> <span class="o">=</span> <span class="nx">tw</span><span class="p">.</span><span class="nx">div</span><span class="sb">`overflow-y-scroll h-screen w-full`</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">SearchOverlay</span> <span class="o">=</span> <span class="nx">styled</span><span class="p">.</span><span class="nx">div</span><span class="sb">`
</span></span></span><span class="line"><span class="cl"><span class="sb">  opacity: </span><span class="si">${</span><span class="nx">props</span> <span class="p">=&gt;</span> <span class="p">(</span><span class="nx">props</span><span class="p">.</span><span class="nx">showSearch</span> <span class="o">?</span> <span class="mi">1</span> <span class="o">:</span> <span class="mi">0</span><span class="p">)</span><span class="si">}</span><span class="sb">;
</span></span></span><span class="line"><span class="cl"><span class="sb">  display: </span><span class="si">${</span><span class="nx">props</span> <span class="p">=&gt;</span> <span class="p">(</span><span class="nx">props</span><span class="p">.</span><span class="nx">showSearch</span> <span class="o">?</span> <span class="s2">&#34;flex&#34;</span> <span class="o">:</span> <span class="s2">&#34;none&#34;</span><span class="p">)</span><span class="si">}</span><span class="sb">;
</span></span></span><span class="line"><span class="cl"><span class="sb">  transition: opacity 150ms linear 0s;
</span></span></span><span class="line"><span class="cl"><span class="sb">  background: rgba(255, 255, 255, 0.9);
</span></span></span><span class="line"><span class="cl"><span class="sb">  </span><span class="si">${</span><span class="nx">tw</span><span class="sb">`fixed inset-0 bg-opacity-50 z-50 m-0 items-center justify-center h-screen w-screen`</span><span class="si">}</span><span class="sb">;
</span></span></span><span class="line"><span class="cl"><span class="sb">`</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">SearchBar</span>
</span></span></code></pre></div><p>Normally I would use a search icon which when pressed would show the search overlay. However to keep things simple we
will just use the text &ldquo;Search&rdquo;, which when clicked on will show our search overlay to the client.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">h1</span>
</span></span><span class="line"><span class="cl">  <span class="na">className</span><span class="o">=</span><span class="s">&#34;hover:cursor-pointer text-orange-800 text-2xl my-10&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="na">onClick</span><span class="o">=</span><span class="p">{()</span> <span class="p">=&gt;</span> <span class="nx">setShowSearch</span><span class="p">(</span><span class="o">!</span><span class="nx">showSearch</span><span class="p">)}</span>
</span></span><span class="line"><span class="cl"><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">Search</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">h1</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>The main job of this component is to toggle the search on/off. To do this we use a state hook like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kr">const</span> <span class="p">[</span><span class="nx">showSearch</span><span class="p">,</span> <span class="nx">setShowSearch</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">hideSearch</span><span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">placeholder</span> <span class="o">!==</span> <span class="s2">&#34;Search&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">setShowSearch</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Where we have a function to hide the search if the user clicks anything outside of the search. Hence the if statement
<code>event.target.placeholder</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">StaticQuery</span>
</span></span><span class="line"><span class="cl">  <span class="na">query</span><span class="o">=</span><span class="p">{</span><span class="nx">graphql</span><span class="sb">`
</span></span></span><span class="line"><span class="cl"><span class="sb">    query SearchIndexQuery {
</span></span></span><span class="line"><span class="cl"><span class="sb">      siteSearchIndex {
</span></span></span><span class="line"><span class="cl"><span class="sb">        index
</span></span></span><span class="line"><span class="cl"><span class="sb">      }
</span></span></span><span class="line"><span class="cl"><span class="sb">    }
</span></span></span><span class="line"><span class="cl"><span class="sb">  `</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="na">render</span><span class="o">=</span><span class="p">{(</span><span class="nx">data</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">SearchContainer</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span><span class="nx">showSearch</span> <span class="o">&amp;&amp;</span> <span class="p">&lt;</span><span class="nt">Search</span> <span class="na">searchIndex</span><span class="o">=</span><span class="p">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">siteSearchIndex</span><span class="p">.</span><span class="nx">index</span><span class="p">}</span> <span class="p">/&gt;}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">SearchContainer</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">)}</span>
</span></span><span class="line"><span class="cl"><span class="p">/&gt;</span>
</span></span></code></pre></div><p>The next interesting part is the Graphql query to get the search index from <code>elasticlunr</code>. We this pass as <code>searchIndex</code>
prop to our <code>Search</code> component we created above. This is the same search index we search against the current user
query. We also use conditional rendering we only show the <code>Search</code> component when <code>showSearch</code> is true.</p>
<p>And that&rsquo;s it! We successfully added search to our <code>Gatsby</code> blog alongside search highlighting. Thanks for reading.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2020-08-20-how-to-add-offline-search-to-a-gatsby-blog/source_code">Example source code</a></li>
<li><a href="https://gitlab.com/hmajid2301/personal-site/-/blob/d5f413310d4404fc6a1761a592f5e10840fc30df/src/components/organisms/SearchBar/SearchBar.tsx">Example Project</a></li>
<li><a href="https://unsplash.com/@markuswinkler?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Cover Photo by Markus Winkler</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>TailwindCSS with CSS variables</title>
      <link>https://haseebmajid.dev/posts/2020-08-05-tailwindcss-with-css-variables/</link>
      <pubDate>Wed, 05 Aug 2020 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2020-08-05-tailwindcss-with-css-variables/</guid>
      <description>&lt;p&gt;TailwindCSS allows us to use pre-defined classes instead of defining our CSS styles. In this article, we will go over
how we can use Custom properties (sometimes referred to as CSS variables or cascading variables) with TailwindCSS.&lt;/p&gt;
&lt;h2 id=&#34;setup&#34;&gt;Setup&lt;/h2&gt;
&lt;p&gt;First, follow the installation guide found &lt;a href=&#34;https://tailwindcss.com/docs/installation/#2-add-tailwind-to-your-css&#34;&gt;here&lt;/a&gt;.
This will show you how you can add TailwindCSS to your current project. For part 2 I will assume you called your CSS
file &lt;code&gt;global.css&lt;/code&gt;. This is the file that contains &lt;code&gt;@tailwind base;&lt;/code&gt; etc.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>TailwindCSS allows us to use pre-defined classes instead of defining our CSS styles. In this article, we will go over
how we can use Custom properties (sometimes referred to as CSS variables or cascading variables) with TailwindCSS.</p>
<h2 id="setup">Setup</h2>
<p>First, follow the installation guide found <a href="https://tailwindcss.com/docs/installation/#2-add-tailwind-to-your-css">here</a>.
This will show you how you can add TailwindCSS to your current project. For part 2 I will assume you called your CSS
file <code>global.css</code>. This is the file that contains <code>@tailwind base;</code> etc.</p>
<h2 id="global-css">Global CSS</h2>
<p>First, we need to edit our TailwindCSS file so it looks something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="p">@</span><span class="k">tailwind</span> <span class="nt">base</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">@</span><span class="k">tailwind</span> <span class="nt">components</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">@</span><span class="k">tailwind</span> <span class="nt">utilities</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">.</span><span class="nc">root</span><span class="o">,</span>
</span></span><span class="line"><span class="cl"><span class="p">#</span><span class="nn">root</span><span class="o">,</span>
</span></span><span class="line"><span class="cl"><span class="p">#</span><span class="nn">docs-root</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nv">--primary</span><span class="p">:</span> <span class="mh">#367ee9</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nv">--secondary</span><span class="p">:</span> <span class="mh">#a0aec0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nv">--accent</span><span class="p">:</span> <span class="mh">#718096</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nv">--background</span><span class="p">:</span> <span class="mh">#fff</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nv">--main</span><span class="p">:</span> <span class="mh">#0d0106</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nv">--header</span><span class="p">:</span> <span class="mh">#2d3748</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>I wrap my entire body in an element with class <code>root</code> or id <code>root</code>, so that any of my elements can access it later.</p>
<h3 id="gatsby">Gatsby</h3>
<p>If you&rsquo;re using Gatsby, you can add the following to your <code>gatsby-browser.js</code> file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="nx">wrapRootElement</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">element</span> <span class="p">})</span> <span class="p">=&gt;</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="o">&lt;</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="s2">&#34;root overflow-hidden&#34;</span><span class="o">&gt;</span><span class="p">{</span><span class="nx">element</span><span class="p">}</span><span class="o">&lt;</span><span class="err">/div&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span></code></pre></div><p>This will wrap all of our pages in the class <code>root</code> and <code>overflow-hidden</code> CSS class from TailwindCSS.</p>
<h2 id="tailwindconfigjs">tailwind.config.js</h2>
<p>Now we&rsquo;ve defined some CSS variables how can we use them with Tailwindcss? Simple, we update our tailwind config file
with some of the new CSS variables. Here we simply want to extend the config to add new colour values.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">theme</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">extend</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">colors</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">primary</span><span class="o">:</span> <span class="s2">&#34;var(--primary)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">secondary</span><span class="o">:</span> <span class="s2">&#34;var(--secondary)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">main</span><span class="o">:</span> <span class="s2">&#34;var(--main)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">background</span><span class="o">:</span> <span class="s2">&#34;var(--background)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">header</span><span class="o">:</span> <span class="s2">&#34;var(--header)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">accent</span><span class="o">:</span> <span class="s2">&#34;var(--accent)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>The syntax is very similar to how we would use the variables normally with CSS where it would normally look like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="nt">element</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">background-color</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="n">primary</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h2 id="logo">Logo</h2>
<p>Now how do we use our variable? Again pretty straight forward just like our normal tailwind classes. Let&rsquo;s imagine
we have a React component called <code>Logo.tsx</code>, defined like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span> <span class="kr">from</span> <span class="s2">&#34;react&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">tw</span> <span class="kr">from</span> <span class="s2">&#34;twin.macro&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">interface</span> <span class="nx">Props</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/** The size of the main text  */</span>
</span></span><span class="line"><span class="cl">  <span class="nx">size?</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">Logo</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">size</span> <span class="o">=</span> <span class="s2">&#34;2xl&#34;</span> <span class="p">}</span><span class="o">:</span> <span class="nx">Props</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">LogoContainer</span> <span class="na">className</span><span class="o">=</span><span class="p">{</span><span class="sb">` md:text-</span><span class="si">${</span><span class="nx">size</span><span class="si">}</span><span class="sb">`</span><span class="p">}&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">Tag</span><span class="p">&gt;</span><span class="o">&amp;</span><span class="nx">lt</span><span class="p">;&lt;/</span><span class="nt">Tag</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">Haseeb</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">Tag</span><span class="p">&gt;</span><span class="o">/&amp;</span><span class="nx">gt</span><span class="p">;&lt;/</span><span class="nt">Tag</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">LogoContainer</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">LogoContainer</span> <span class="o">=</span> <span class="nx">tw</span><span class="p">.</span><span class="nx">div</span><span class="sb">`cursor-pointer font-header tracking-wide text-2xl font-bold hover:text-primary`</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">Tag</span> <span class="o">=</span> <span class="nx">tw</span><span class="p">.</span><span class="nx">span</span><span class="sb">`text-accent`</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">Logo</span><span class="p">;</span>
</span></span></code></pre></div><blockquote>
<p>INFO: I&rsquo;m using the <code>twin.macro</code> the library so we can use it with CSS-in-JS.</p>
</blockquote>
<p>To use our variables we just use them like: <code>text-primary</code>. Which will use the value we defined above, <code>#367ee9</code>. Now
if we change the value in the <code>global.css</code> file, it will automatically change here as well.</p>
<h2 id="darklight-mode-optional">Dark/Light Mode (Optional)</h2>
<p>This can be easily extended to add a dark/light mode. Add the following to the <code>global.css</code> file like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="p">.</span><span class="nc">theme-light</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nv">--background</span><span class="p">:</span> <span class="mh">#fff</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nv">--main</span><span class="p">:</span> <span class="mh">#0d0106</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nv">--header</span><span class="p">:</span> <span class="mh">#2d3748</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">.</span><span class="nc">theme-dark</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nv">--background</span><span class="p">:</span> <span class="mh">#0e141b</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nv">--main</span><span class="p">:</span> <span class="mh">#ffffff</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nv">--header</span><span class="p">:</span> <span class="mh">#eaeaea</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>We can use a theme context to get the current theme I&rsquo;ve written about
<a href="https://dev.to/hmajid2301/react-hooks-context-local-storage-3job">here</a>. We get the current theme then use that to determine which class
to set. This will then change value of the variables. If the theme changes, the variable values will change dark -&gt; light or
light -&gt; dark etc.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kr">const</span> <span class="p">{</span> <span class="nx">theme</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">useContext</span><span class="p">(</span><span class="nx">ThemeContext</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">div</span>
</span></span><span class="line"><span class="cl">    <span class="na">className</span><span class="o">=</span><span class="p">{</span><span class="sb">`</span><span class="si">${</span>
</span></span><span class="line"><span class="cl">      <span class="nx">theme</span> <span class="o">===</span> <span class="s2">&#34;light&#34;</span> <span class="o">?</span> <span class="s2">&#34;theme-light&#34;</span> <span class="o">:</span> <span class="s2">&#34;theme-dark&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="si">}</span><span class="sb"> bg-background`</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span></code></pre></div><p>That&rsquo;s it! We&rsquo;ve learnt how to use CSS variables with TailwindCSS.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/personal-site/-/tree/fa01433eecec728427763e1e2b2cdd9710a9c197">Example Project</a></li>
<li><a href="https://flaticon.com">Icons from FlatIcon</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to use Storybooks with MDX</title>
      <link>https://haseebmajid.dev/posts/2020-07-20-how-to-use-storybooks-with-mdx/</link>
      <pubDate>Mon, 20 Jul 2020 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2020-07-20-how-to-use-storybooks-with-mdx/</guid>
      <description>&lt;p&gt;This article (sort of) continues on from my previous article
&lt;a href=&#34;https://haseebmajid.dev/posts/2020-06-29-how-to-use-storybooks-gatsby-babel-tailwind-typescript-together/&#34;&gt;How to use Storybooks, Gatsby, Babel, Tailwind, Typescript together&lt;/a&gt;.
In this article, we will document our React components using Storybook with MDX.&lt;/p&gt;
&lt;p&gt;You can find an example project using this &lt;a href=&#34;https://gitlab.com/hmajid2301/personal-site/-/tree/e415420744b2a8f49eddaf2d3058b23c70f46638/.storybook&#34;&gt;here&lt;/a&gt;,
you can also find a &lt;a href=&#34;https://storybook.haseebmajid.dev/&#34;&gt;demo site&lt;/a&gt; for said project.&lt;/p&gt;
&lt;h2 id=&#34;prerequisite&#34;&gt;Prerequisite&lt;/h2&gt;
&lt;p&gt;Just to make sure everyone&amp;rsquo;s on the same page let&amp;rsquo;s follow the same steps to setup Storybook as we had in the last article.
We will use the latest versions of Storybook (v6) so we can access the latest features. We will go over how we can use
these features in the next article.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>This article (sort of) continues on from my previous article
<a href="/posts/2020-06-29-how-to-use-storybooks-gatsby-babel-tailwind-typescript-together/">How to use Storybooks, Gatsby, Babel, Tailwind, Typescript together</a>.
In this article, we will document our React components using Storybook with MDX.</p>
<p>You can find an example project using this <a href="https://gitlab.com/hmajid2301/personal-site/-/tree/e415420744b2a8f49eddaf2d3058b23c70f46638/.storybook">here</a>,
you can also find a <a href="https://storybook.haseebmajid.dev/">demo site</a> for said project.</p>
<h2 id="prerequisite">Prerequisite</h2>
<p>Just to make sure everyone&rsquo;s on the same page let&rsquo;s follow the same steps to setup Storybook as we had in the last article.
We will use the latest versions of Storybook (v6) so we can access the latest features. We will go over how we can use
these features in the next article.</p>
<p>First, remove any lines in your <code>package.json</code> that start with <code>@storybook</code>. In my case,
I removed <code>@storybook/addon-actions</code>, <code>@storybook/add-links</code>, <code>@storybook/addons</code> and
<code>@storybook/react</code>. The typescript docgen modules will be used to parse our components and retrieve the props information
for Storybook. We will see a bit later with the control addon.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">yarn add --dev @storybook/addon-docs@6.0.0-beta.20 @storybook/addon-essentials@6.0.0-beta.20 <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>@storybook/addon-storysource@6.0.0-beta.20  @storybook/preset-typescript@1.2.0 <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>@storybook/react@6.0.0-beta.20 core-js@2.6.5 react-docgen-typescript@1.16.5 <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>react-docgen-typescript-loader@3.6.0
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">npx -p @storybook/cli sb init -f
</span></span><span class="line"><span class="cl">vim .storybook/main.js
</span></span><span class="line"><span class="cl">vim .storybook/preview.js
</span></span><span class="line"><span class="cl">vim preview-head.html
</span></span><span class="line"><span class="cl">vim webpack.config.js
</span></span></code></pre></div><p>Next, we will update the <code>main.js</code> file. This will tell Storybook where to look for the stories, in this case in the <code>src</code> folder
any file called <code>x.stories.mdx</code> or <code>x.stories.tsx</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">stories</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;../src/**/*.stories.@(tsx|mdx)&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nx">addons</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;@storybook/addon-essentials&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;@storybook/addon-docs&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;@storybook/preset-typescript&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>Next, let&rsquo;s update the preview file. Here you can define global parameters and decorators. Again
will see more of this in the next article.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span> <span class="nx">from</span> <span class="s1">&#39;react&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">withA11y</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;@storybook/addon-a11y&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">action</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;@storybook/addon-actions&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">configure</span><span class="p">,</span> <span class="nx">addDecorator</span><span class="p">,</span> <span class="nx">addParameters</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;@storybook/react&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">INITIAL_VIEWPORTS</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;@storybook/addon-viewport&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// We will address this later.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">import</span> <span class="s1">&#39;../src/styles/globals.css&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="s1">&#39;./main.css&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// automatically import all files ending in *.stories.js
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">configure</span><span class="p">(</span><span class="nx">require</span><span class="p">.</span><span class="nx">context</span><span class="p">(</span><span class="s1">&#39;../src&#39;</span><span class="p">,</span> <span class="kc">true</span><span class="p">,</span> <span class="sr">/\.stories\.mdx$/</span><span class="p">),</span> <span class="nx">module</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Gatsby Setup
</span></span></span><span class="line"><span class="cl"><span class="c1">// ============================================
</span></span></span><span class="line"><span class="cl"><span class="c1">// Gatsby&#39;s Link overrides:
</span></span></span><span class="line"><span class="cl"><span class="c1">// Gatsby defines a global called ___loader to prevent its method calls from creating console errors you override it here
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">global</span><span class="p">.</span><span class="nx">___loader</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">enqueue</span><span class="o">:</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{},</span>
</span></span><span class="line"><span class="cl">  <span class="nx">hovering</span><span class="o">:</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{},</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="c1">// Gatsby internal mocking to prevent unnecessary errors in storybook testing environment
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">global</span><span class="p">.</span><span class="nx">__PATH_PREFIX__</span> <span class="o">=</span> <span class="s1">&#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="c1">// This is to utilized to override the window.___navigate method Gatsby defines and uses to report what path a Link would be taking us to if it wasn&#39;t inside a storybook
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nb">window</span><span class="p">.</span><span class="nx">___navigate</span> <span class="o">=</span> <span class="p">(</span><span class="nx">pathname</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">action</span><span class="p">(</span><span class="s1">&#39;NavigateTo:&#39;</span><span class="p">)(</span><span class="nx">pathname</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Storybook Addons
</span></span></span><span class="line"><span class="cl"><span class="c1">// ============================================
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">addParameters</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nx">viewport</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">viewports</span><span class="o">:</span> <span class="nx">INITIAL_VIEWPORTS</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">defaultViewport</span><span class="o">:</span> <span class="s1">&#39;responsive&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nx">options</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">panelPosition</span><span class="o">:</span> <span class="s1">&#39;right&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">storySort</span><span class="o">:</span> <span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">)</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="nx">a</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="nx">kind</span> <span class="o">===</span> <span class="nx">b</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="nx">kind</span>
</span></span><span class="line"><span class="cl">        <span class="o">?</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">        <span class="o">:</span> <span class="nx">a</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="nx">id</span><span class="p">.</span><span class="nx">localeCompare</span><span class="p">(</span><span class="nx">b</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="nx">id</span><span class="p">,</span> <span class="kc">undefined</span><span class="p">,</span> <span class="p">{</span> <span class="nx">numeric</span><span class="o">:</span> <span class="kc">true</span> <span class="p">}),</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nx">inline</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Storybook Decorators
</span></span></span><span class="line"><span class="cl"><span class="c1">// ============================================
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">addDecorator</span><span class="p">(</span><span class="nx">withA11y</span><span class="p">);</span>
</span></span></code></pre></div><p>If we want to use any custom fonts, such as google fonts or other styles within our Tailwind, we need to
define them here.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">link</span>
</span></span><span class="line"><span class="cl">  <span class="na">href</span><span class="o">=</span><span class="s">&#34;https://fonts.googleapis.com/css2?family=Inter:wght@600,900&amp;display=swap&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="na">rel</span><span class="o">=</span><span class="s">&#34;stylesheet&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">/&gt;</span>
</span></span></code></pre></div><p>Storybook uses webpack, so if we want to add extra webpack options, we do that here. This allows us to use
things like Babel and PostCSS loader.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">config</span> <span class="p">})</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">config</span><span class="p">.</span><span class="nx">module</span><span class="p">.</span><span class="nx">rules</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">use</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">loader</span> <span class="o">=</span> <span class="nx">require</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="s2">&#34;babel-loader&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nx">config</span><span class="p">.</span><span class="nx">module</span><span class="p">.</span><span class="nx">rules</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">use</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">options</span><span class="p">.</span><span class="nx">presets</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="nx">require</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="s2">&#34;@babel/preset-react&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nx">require</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="s2">&#34;@babel/preset-env&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">config</span><span class="p">.</span><span class="nx">module</span><span class="p">.</span><span class="nx">rules</span><span class="p">.</span><span class="nx">push</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nx">test</span><span class="o">:</span> <span class="sr">/\.(ts|tsx)$/</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">loader</span><span class="o">:</span> <span class="nx">require</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="s2">&#34;babel-loader&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nx">options</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">presets</span><span class="o">:</span> <span class="p">[[</span><span class="s2">&#34;react-app&#34;</span><span class="p">,</span> <span class="p">{</span> <span class="nx">flow</span><span class="o">:</span> <span class="kc">false</span><span class="p">,</span> <span class="nx">typescript</span><span class="o">:</span> <span class="kc">true</span> <span class="p">}]],</span>
</span></span><span class="line"><span class="cl">      <span class="nx">plugins</span><span class="o">:</span> <span class="p">[],</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">config</span><span class="p">.</span><span class="nx">module</span><span class="p">.</span><span class="nx">rules</span><span class="p">.</span><span class="nx">push</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nx">test</span><span class="o">:</span> <span class="sr">/\.css$/</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">use</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">loader</span><span class="o">:</span> <span class="s2">&#34;postcss-loader&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">options</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nx">sourceMap</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nx">config</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">path</span><span class="o">:</span> <span class="s2">&#34;./.storybook/&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="nx">config</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><h2 id="add-ons">Add-ons</h2>
<p>Let&rsquo;s install the extra add-ons we will use with storybooks:</p>
<ul>
<li><a href="https://github.com/storybookjs/storybook/tree/next/addons/a11y">a11y</a>: Will list any accessibility issues with your component</li>
<li><a href="https://github.com/storybookjs/storybook/tree/next/addons/controls">controls</a>: Is the new version of the addon-knobs and will let you control the props you pass in</li>
<li><a href="https://github.com/storybookjs/storybook/tree/next/addons/viewport">viewport</a>: Allows you to test how your component looks, with different viewports like iPhone, 13&quot; etc</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">yarn add --dev @storybook/addon-a11y@6.0.0-beta.20 @storybook/addon-controls@6.0.0-beta.15 <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>                @storybook/addon-viewport@6.0.0-beta.20
</span></span></code></pre></div><p>Let&rsquo;s update the <code>.storybook/main.js</code> so that Storybook uses the new addons we&rsquo;ve just installed.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">stories</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;../src/**/*.stories.@(tsx|mdx)&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nx">addons</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;@storybook/addon-a11y&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;@storybook/addon-controls&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;@storybook/addon-essentials&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;@storybook/preset-typescript&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;@storybook/addon-viewport&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>Next, let&rsquo;s update <code>.storybook/preview.js</code> so it looks like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span> <span class="nx">from</span> <span class="s1">&#39;react&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">withA11y</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;@storybook/addon-a11y&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">action</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;@storybook/addon-actions&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">configure</span><span class="p">,</span> <span class="nx">addDecorator</span><span class="p">,</span> <span class="nx">addParameters</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;@storybook/react&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">INITIAL_VIEWPORTS</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;@storybook/addon-viewport&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// We will address this later.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">import</span> <span class="s1">&#39;../src/styles/globals.css&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="s1">&#39;./main.css&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// automatically import all files ending in *.stories.js
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">configure</span><span class="p">(</span><span class="nx">require</span><span class="p">.</span><span class="nx">context</span><span class="p">(</span><span class="s1">&#39;../src&#39;</span><span class="p">,</span> <span class="kc">true</span><span class="p">,</span> <span class="sr">/\.stories\.mdx$/</span><span class="p">),</span> <span class="nx">module</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Gatsby Setup
</span></span></span><span class="line"><span class="cl"><span class="c1">// ============================================
</span></span></span><span class="line"><span class="cl"><span class="c1">// Gatsby&#39;s Link overrides:
</span></span></span><span class="line"><span class="cl"><span class="c1">// Gatsby defines a global called ___loader to prevent its method calls from creating console errors you override it here
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">global</span><span class="p">.</span><span class="nx">___loader</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">enqueue</span><span class="o">:</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{},</span>
</span></span><span class="line"><span class="cl">  <span class="nx">hovering</span><span class="o">:</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{},</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="c1">// Gatsby internal mocking to prevent unnecessary errors in storybook testing environment
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">global</span><span class="p">.</span><span class="nx">__PATH_PREFIX__</span> <span class="o">=</span> <span class="s1">&#39;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="c1">// This is to utilized to override the window.___navigate method Gatsby defines and uses to report what path a Link would be taking us to if it wasn&#39;t inside a storybook
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nb">window</span><span class="p">.</span><span class="nx">___navigate</span> <span class="o">=</span> <span class="p">(</span><span class="nx">pathname</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">action</span><span class="p">(</span><span class="s1">&#39;NavigateTo:&#39;</span><span class="p">)(</span><span class="nx">pathname</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Storybook Addons
</span></span></span><span class="line"><span class="cl"><span class="c1">// ============================================
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">addParameters</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nx">viewport</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">viewports</span><span class="o">:</span> <span class="nx">INITIAL_VIEWPORTS</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">defaultViewport</span><span class="o">:</span> <span class="s1">&#39;responsive&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nx">options</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">panelPosition</span><span class="o">:</span> <span class="s1">&#39;right&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">storySort</span><span class="o">:</span> <span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">)</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="nx">a</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="nx">kind</span> <span class="o">===</span> <span class="nx">b</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="nx">kind</span>
</span></span><span class="line"><span class="cl">        <span class="o">?</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">        <span class="o">:</span> <span class="nx">a</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="nx">id</span><span class="p">.</span><span class="nx">localeCompare</span><span class="p">(</span><span class="nx">b</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="nx">id</span><span class="p">,</span> <span class="kc">undefined</span><span class="p">,</span> <span class="p">{</span> <span class="nx">numeric</span><span class="o">:</span> <span class="kc">true</span> <span class="p">}),</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nx">inline</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Storybook Decorators
</span></span></span><span class="line"><span class="cl"><span class="c1">// ============================================
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">addDecorator</span><span class="p">(</span><span class="nx">withA11y</span><span class="p">);</span>
</span></span></code></pre></div><p>The following snippet allows Storybook to show the default list of viewports. You can customise this list by following
<a href="https://github.com/storybookjs/storybook/tree/next/addons/viewport">this README here</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">viewport</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">viewports</span><span class="o">:</span> <span class="nx">INITIAL_VIEWPORTS</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">defaultViewport</span><span class="o">:</span> <span class="s2">&#34;responsive&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This part of the <code>addParameters</code> sorts all of our storybook components in descending capital order (in the left panel).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">options</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">panelPosition</span><span class="o">:</span> <span class="s2">&#34;right&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">storySort</span><span class="o">:</span> <span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">)</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">a</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="nx">kind</span> <span class="o">===</span> <span class="nx">b</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="nx">kind</span>
</span></span><span class="line"><span class="cl">      <span class="o">?</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">      <span class="o">:</span> <span class="nx">a</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="nx">id</span><span class="p">.</span><span class="nx">localeCompare</span><span class="p">(</span><span class="nx">b</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="nx">id</span><span class="p">,</span> <span class="kc">undefined</span><span class="p">,</span> <span class="p">{</span> <span class="nx">numeric</span><span class="o">:</span> <span class="kc">true</span> <span class="p">}),</span>
</span></span><span class="line"><span class="cl"><span class="p">},</span>
</span></span></code></pre></div><p>The final part will add a panel which will list accessibility (a11y) issues with our components, such as missing an
<code>alt</code> in an <code>&lt;img&gt;</code> tag.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">addDecorator</span><span class="p">(</span><span class="nx">withA11y</span><span class="p">);</span>
</span></span></code></pre></div><h2 id="mdx">MDX</h2>
<p>MDX is markdown mixed with JSX, it lets us render &ldquo;React&rdquo; code within markdown files, whilst providing all the features
of markdown as well as headers and hyperlinks. So I feel it is a perfect way to document your Storybooks.</p>
<h3 id="introduction-mdx">Introduction MDX</h3>
<p>This file is an example of a story which will not render any of our components. It&rsquo;s simply there for documentation. This
file will list the colour palette of the app. It will get the colours from our <code>tailwind.config.js</code> (mixed with the default one).
So if you changed the values in the tailwind config file, it&rsquo;ll also change them in this story.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl">import {
</span></span><span class="line"><span class="cl">  Meta,
</span></span><span class="line"><span class="cl">  ColorPalette,
</span></span><span class="line"><span class="cl">  ColorItem,
</span></span><span class="line"><span class="cl">  Typeset,
</span></span><span class="line"><span class="cl">} from &#34;@storybook/addon-docs/blocks&#34;;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">import { Themes } from &#34;~/styles&#34;;
</span></span><span class="line"><span class="cl">import { theme } from &#34;~/utils/tailwindConfig&#34;;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">Meta</span> <span class="na">title</span><span class="o">=</span><span class="s">&#34;Introduction&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gh"># Introduction
</span></span></span><span class="line"><span class="cl"><span class="gh"></span>
</span></span><span class="line"><span class="cl">This project uses Tailwindcss so a lot of colour props are passed in as CSS classes that
</span></span><span class="line"><span class="cl">Tailwind applies styling to i.e. <span class="sb">`text-blue-500`</span> to use the primary blue. You can find the
</span></span><span class="line"><span class="cl">full colour palette [<span class="nt">here</span>](<span class="na">https://tailwindcss.com/docs/customizing-colors/#default-color-palette</span>).
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">## Colours
</span></span></span><span class="line"><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="cl"><span class="gu">### Light Theme
</span></span></span><span class="line"><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">ColorPalette</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">ColorItem</span>
</span></span><span class="line"><span class="cl">    <span class="na">title</span><span class="o">=</span><span class="s">&#34;theme.color.background&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="na">subtitle</span><span class="o">=</span><span class="s">&#34;White&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="na">colors</span><span class="o">=</span><span class="s">{[theme.colors.white]}</span>
</span></span><span class="line"><span class="cl">  <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">ColorItem</span>
</span></span><span class="line"><span class="cl">    <span class="na">title</span><span class="o">=</span><span class="s">&#34;theme.color.text&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="na">subtitle</span><span class="o">=</span><span class="s">&#34;Black&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="na">colors</span><span class="o">=</span><span class="s">{[theme.colors.black]}</span>
</span></span><span class="line"><span class="cl">  <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">ColorItem</span>
</span></span><span class="line"><span class="cl">    <span class="na">title</span><span class="o">=</span><span class="s">&#34;theme.color.header&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="na">subtitle</span><span class="o">=</span><span class="s">&#34;Grey&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="na">colors</span><span class="o">=</span><span class="s">{[theme.colors.gray[700]]}</span>
</span></span><span class="line"><span class="cl">  <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">ColorPalette</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">### Dark Theme
</span></span></span><span class="line"><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">ColorPalette</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">ColorItem</span>
</span></span><span class="line"><span class="cl">    <span class="na">title</span><span class="o">=</span><span class="s">&#34;theme.color.background&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="na">subtitle</span><span class="o">=</span><span class="s">&#34;White&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="na">colors</span><span class="o">=</span><span class="s">{[theme.colors.gray[900]]}</span>
</span></span><span class="line"><span class="cl">  <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">ColorItem</span>
</span></span><span class="line"><span class="cl">    <span class="na">title</span><span class="o">=</span><span class="s">&#34;theme.color.text&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="na">subtitle</span><span class="o">=</span><span class="s">&#34;White&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="na">colors</span><span class="o">=</span><span class="s">{[theme.colors.white]}</span>
</span></span><span class="line"><span class="cl">  <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">ColorItem</span>
</span></span><span class="line"><span class="cl">    <span class="na">title</span><span class="o">=</span><span class="s">&#34;theme.color.header&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="na">subtitle</span><span class="o">=</span><span class="s">&#34;Grey&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="na">colors</span><span class="o">=</span><span class="s">{[theme.colors.gray[200]]}</span>
</span></span><span class="line"><span class="cl">  <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">ColorPalette</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">### Shared
</span></span></span><span class="line"><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">ColorPalette</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">ColorItem</span>
</span></span><span class="line"><span class="cl">    <span class="na">title</span><span class="o">=</span><span class="s">&#34;theme.color.primary&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="na">subtitle</span><span class="o">=</span><span class="s">&#34;Blue&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="na">colors</span><span class="o">=</span><span class="s">{[theme.colors.blue[500]]}</span>
</span></span><span class="line"><span class="cl">  <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">ColorItem</span>
</span></span><span class="line"><span class="cl">    <span class="na">title</span><span class="o">=</span><span class="s">&#34;theme.color.secondary&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="na">subtitle</span><span class="o">=</span><span class="s">&#34;Orange&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="na">colors</span><span class="o">=</span><span class="s">{[theme.colors.orange[500]]}</span>
</span></span><span class="line"><span class="cl">  <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">ColorItem</span>
</span></span><span class="line"><span class="cl">    <span class="na">title</span><span class="o">=</span><span class="s">&#34;theme.color.tertiary&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="na">subtitle</span><span class="o">=</span><span class="s">&#34;Grey&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="na">colors</span><span class="o">=</span><span class="s">{[theme.colors.gray[500]]}</span>
</span></span><span class="line"><span class="cl">  <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">ColorItem</span>
</span></span><span class="line"><span class="cl">    <span class="na">title</span><span class="o">=</span><span class="s">&#34;Monochrome&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="na">colors</span><span class="o">=</span><span class="s">{[</span>
</span></span><span class="line"><span class="cl">      <span class="na">theme</span><span class="err">.</span><span class="na">colors</span><span class="err">.</span><span class="na">monochrome</span><span class="err">[</span><span class="na">900</span><span class="err">],</span>
</span></span><span class="line"><span class="cl">      <span class="na">theme</span><span class="err">.</span><span class="na">colors</span><span class="err">.</span><span class="na">monochrome</span><span class="err">[</span><span class="na">800</span><span class="err">],</span>
</span></span><span class="line"><span class="cl">      <span class="na">theme</span><span class="err">.</span><span class="na">colors</span><span class="err">.</span><span class="na">monochrome</span><span class="err">[</span><span class="na">700</span><span class="err">],</span>
</span></span><span class="line"><span class="cl">      <span class="na">theme</span><span class="err">.</span><span class="na">colors</span><span class="err">.</span><span class="na">monochrome</span><span class="err">[</span><span class="na">600</span><span class="err">],</span>
</span></span><span class="line"><span class="cl">      <span class="na">theme</span><span class="err">.</span><span class="na">colors</span><span class="err">.</span><span class="na">monochrome</span><span class="err">[</span><span class="na">500</span><span class="err">],</span>
</span></span><span class="line"><span class="cl">      <span class="na">theme</span><span class="err">.</span><span class="na">colors</span><span class="err">.</span><span class="na">monochrome</span><span class="err">[</span><span class="na">400</span><span class="err">],</span>
</span></span><span class="line"><span class="cl">      <span class="na">theme</span><span class="err">.</span><span class="na">colors</span><span class="err">.</span><span class="na">monochrome</span><span class="err">[</span><span class="na">300</span><span class="err">],</span>
</span></span><span class="line"><span class="cl">      <span class="na">theme</span><span class="err">.</span><span class="na">colors</span><span class="err">.</span><span class="na">monochrome</span><span class="err">[</span><span class="na">200</span><span class="err">],</span>
</span></span><span class="line"><span class="cl">      <span class="na">theme</span><span class="err">.</span><span class="na">colors</span><span class="err">.</span><span class="na">monochrome</span><span class="err">[</span><span class="na">100</span><span class="err">],</span>
</span></span><span class="line"><span class="cl">    <span class="err">]}</span>
</span></span><span class="line"><span class="cl">  <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">ColorPalette</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">## Typography
</span></span></span><span class="line"><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="cl"><span class="gu">### Header
</span></span></span><span class="line"><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="cl"><span class="gs">**Font**</span>: Inter
</span></span><span class="line"><span class="cl"><span class="gs">**Weight**</span>: 900
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">Typeset</span> <span class="na">fontFamily</span><span class="o">=</span><span class="s">&#34;Inter&#34;</span> <span class="na">fontSizes</span><span class="o">=</span><span class="s">{[10,</span> <span class="na">20</span><span class="err">,</span> <span class="na">30</span><span class="err">,</span> <span class="na">40</span><span class="err">,</span> <span class="na">50</span><span class="err">]}</span> <span class="na">fontWeight</span><span class="o">=</span><span class="s">{900}</span> <span class="p">/&gt;</span>
</span></span></code></pre></div><p>The first part contains our imports so we can render the colours correctly. It also includes a meta tag where we list
the title of the page, each of our stories should have one of these meta tags.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl">import {
</span></span><span class="line"><span class="cl">Meta,
</span></span><span class="line"><span class="cl">ColorPalette,
</span></span><span class="line"><span class="cl">ColorItem,
</span></span><span class="line"><span class="cl">Typeset,
</span></span><span class="line"><span class="cl">} from &#34;@storybook/addon-docs/blocks&#34;;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">import { Themes } from &#34;~/styles&#34;;
</span></span><span class="line"><span class="cl">import { theme } from &#34;~/utils/tailwindConfig&#34;;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">Meta</span> <span class="na">title</span><span class="o">=</span><span class="s">&#34;Introduction&#34;</span> <span class="p">/&gt;</span>
</span></span></code></pre></div><h4 id="utils">Utils</h4>
<p>We use this simple script to resolve the values of the tailwind config so we can use them like so
<code>theme.colors.orange[500]</code>. We pass the script our current config, it then combines it with the default config and
generates a final tailwind config. From this, we get the theme part as this is all we need to retrieve our colours.
So with a class name of say <code>text-blue-500</code> to get the colour, we would do <code>theme.colors.blue[500]</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">resolveConfig</span> <span class="nx">from</span> <span class="s1">&#39;tailwindcss/resolveConfig&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">tailwindConfig</span> <span class="nx">from</span> <span class="s1">&#39;../../tailwind.config&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">config</span> <span class="o">=</span> <span class="nx">resolveConfig</span><span class="p">(</span><span class="nx">tailwindConfig</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="p">{</span> <span class="nx">theme</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">config</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">config</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="p">{</span> <span class="nx">theme</span> <span class="p">};</span>
</span></span></code></pre></div><h2 id="component">Component</h2>
<p>Now let&rsquo;s take a look at how to create a Storybook story for one of our components.</p>
<h3 id="logo">Logo</h3>
<p>Say we have a component that looks like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span> <span class="kr">from</span> <span class="s1">&#39;react&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">tw</span> <span class="kr">from</span> <span class="s1">&#39;twin.macro&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">interface</span> <span class="nx">Props</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/** The colour of the opening and closing tags. */</span>
</span></span><span class="line"><span class="cl">  <span class="nx">accent?</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/** The colour of main text. */</span>
</span></span><span class="line"><span class="cl">  <span class="nx">color?</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/** The colour when you hover over the logo. */</span>
</span></span><span class="line"><span class="cl">  <span class="nx">hoverColor?</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/** The main text of the logo for example, your name. */</span>
</span></span><span class="line"><span class="cl">  <span class="nx">text</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/** The size of the main text  */</span>
</span></span><span class="line"><span class="cl">  <span class="nx">size</span><span class="o">?:</span> <span class="s1">&#39;xs&#39;</span> <span class="o">|</span> <span class="s1">&#39;sm&#39;</span> <span class="o">|</span> <span class="s1">&#39;lg&#39;</span> <span class="o">|</span> <span class="s1">&#39;xl&#39;</span> <span class="o">|</span> <span class="s1">&#39;2xl&#39;</span> <span class="o">|</span> <span class="s1">&#39;3xl&#39;</span> <span class="o">|</span> <span class="s1">&#39;4xl&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">Logo</span> <span class="o">=</span> <span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nx">accent</span> <span class="o">=</span> <span class="s1">&#39;black&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">color</span> <span class="o">=</span> <span class="s1">&#39;black&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">hoverColor</span> <span class="o">=</span> <span class="s1">&#39;blue-500&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">text</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">size</span> <span class="o">=</span> <span class="s1">&#39;2xl&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span><span class="o">:</span> <span class="nx">Props</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">LogoContainer</span>
</span></span><span class="line"><span class="cl">    <span class="na">className</span><span class="o">=</span><span class="p">{</span><span class="sb">`hover:text-</span><span class="si">${</span><span class="nx">hoverColor</span><span class="si">}</span><span class="sb"> text-</span><span class="si">${</span><span class="nx">color</span><span class="si">}</span><span class="sb"> lg:text-</span><span class="si">${</span><span class="nx">size</span><span class="si">}</span><span class="sb">
</span></span></span><span class="line"><span class="cl"><span class="sb">    md:text-xl sm:text-md text-sm`</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">Tag</span> <span class="na">className</span><span class="o">=</span><span class="p">{</span><span class="sb">`text-</span><span class="si">${</span><span class="nx">accent</span><span class="si">}</span><span class="sb">`</span><span class="p">}</span> <span class="na">data-testid</span><span class="o">=</span><span class="s">&#34;OpeningTag&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="o">&amp;</span><span class="nx">lt</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">Tag</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="nx">text</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">Tag</span> <span class="na">className</span><span class="o">=</span><span class="p">{</span><span class="sb">`text-</span><span class="si">${</span><span class="nx">accent</span><span class="si">}</span><span class="sb">`</span><span class="p">}</span> <span class="na">data-testid</span><span class="o">=</span><span class="s">&#34;ClosingTag&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="o">/&amp;</span><span class="nx">gt</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">Tag</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">LogoContainer</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">LogoContainer</span> <span class="o">=</span> <span class="nx">tw</span><span class="p">.</span><span class="nx">div</span><span class="sb">`cursor-pointer font-header font-black tracking-wide `</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">Tag</span> <span class="o">=</span> <span class="nx">tw</span><span class="p">.</span><span class="nx">span</span><span class="sb">``</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">Logo</span><span class="p">;</span>
</span></span></code></pre></div><p>Since we are using Typescript, we define our props as an interface. The comments above each item in the interface
will be parsed by docgen and shown in our story (we will see this a bit later). It will let the user know what
the prop is so they know how to adjust it. We will also see in a little bit.</p>
<p>The rest of the file is a normal React component, nothing special here. So how do we document this with Storybooks UI?</p>
<h3 id="logostoriesmdx">Logo.stories.mdx</h3>
<p>So now onto the real meat and potatoes of this article, let&rsquo;s document a React component. So I think one of the coolest
parts of the new Storybook is the controls add-on we installed earlier. So how we do this is to provide the props as
<code>args</code> object. Then we pass this onto the component give a name called <code>Basic</code> below.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl">import { Meta, Story, Preview, Props } from &#34;@storybook/addon-docs/blocks&#34;;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">import { theme } from &#34;~/styles&#34;;
</span></span><span class="line"><span class="cl">import Logo from &#34;./Logo&#34;;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">Meta</span> <span class="na">title</span><span class="o">=</span><span class="s">&#34;Logo&#34;</span> <span class="na">component</span><span class="o">=</span><span class="s">{Logo}</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gh"># Logo
</span></span></span><span class="line"><span class="cl"><span class="gh"></span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">Preview</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">Story</span>
</span></span><span class="line"><span class="cl">    <span class="na">name</span><span class="o">=</span><span class="s">&#34;Basic&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="na">args</span><span class="o">=</span><span class="s">{{</span>
</span></span><span class="line"><span class="cl">      <span class="na">accent:</span> <span class="err">&#34;</span><span class="na">gray-500</span><span class="err">&#34;,</span>
</span></span><span class="line"><span class="cl">      <span class="na">color:</span> <span class="err">&#34;</span><span class="na">black</span><span class="err">&#34;,</span>
</span></span><span class="line"><span class="cl">      <span class="na">hoverColor:</span> <span class="err">&#34;</span><span class="na">blue-500</span><span class="err">&#34;,</span>
</span></span><span class="line"><span class="cl">      <span class="na">text:</span> <span class="err">&#34;</span><span class="na">Haseeb</span><span class="err">&#34;,</span>
</span></span><span class="line"><span class="cl">    <span class="err">}}</span>
</span></span><span class="line"><span class="cl"><span class="k">  &gt;
</span></span></span><span class="line"><span class="cl"><span class="k"></span><span class="ge">    {(args) =&gt; &lt;Logo {...args} /&gt;}
</span></span></span><span class="line"><span class="cl"><span class="ge"></span>  <span class="err">&lt;/</span><span class="na">Story</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">Preview</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">Props</span> <span class="na">story</span><span class="o">=</span><span class="s">&#34;Basic&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">## Accent
</span></span></span><span class="line"><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="cl">You can adjust the accent (tags) color by passing the <span class="sb">`accent`</span> prop.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">Preview</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">Story</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;Accent Colour&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">Logo</span>
</span></span><span class="line"><span class="cl">        <span class="na">accent</span><span class="o">=</span><span class="s">&#34;gray-500&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">color</span><span class="o">=</span><span class="s">&#34;blue-500&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">text</span><span class="o">=</span><span class="s">&#34;Haseeb&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">hoverColor</span><span class="o">=</span><span class="s">&#34;blue-500&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">Logo</span>
</span></span><span class="line"><span class="cl">        <span class="na">accent</span><span class="o">=</span><span class="s">&#34;gray-500&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">color</span><span class="o">=</span><span class="s">&#34;black&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">text</span><span class="o">=</span><span class="s">&#34;Haseeb&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">hoverColor</span><span class="o">=</span><span class="s">&#34;blue-500&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">Story</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">Preview</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">## Colour
</span></span></span><span class="line"><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="cl">You can change the default colour of the logo by passing the <span class="sb">`color`</span> prop.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">Preview</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">Story</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;Main Text Colour&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">Logo</span>
</span></span><span class="line"><span class="cl">        <span class="na">accent</span><span class="o">=</span><span class="s">&#34;gray-500&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">color</span><span class="o">=</span><span class="s">&#34;gray-500&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">text</span><span class="o">=</span><span class="s">&#34;Haseeb&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">hoverColor</span><span class="o">=</span><span class="s">&#34;blue-500&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">Logo</span>
</span></span><span class="line"><span class="cl">        <span class="na">accent</span><span class="o">=</span><span class="s">&#34;gray-500&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">color</span><span class="o">=</span><span class="s">&#34;blue-500&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">text</span><span class="o">=</span><span class="s">&#34;Haseeb&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">hoverColor</span><span class="o">=</span><span class="s">&#34;blue-500&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">Story</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">Preview</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">## Hover Color
</span></span></span><span class="line"><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="cl">You can change the hover colour of the logo by passing the <span class="sb">`hoverColor`</span> prop.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">Preview</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">Story</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;Hover Colour&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">Logo</span>
</span></span><span class="line"><span class="cl">        <span class="na">accent</span><span class="o">=</span><span class="s">&#34;gray-500&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">color</span><span class="o">=</span><span class="s">&#34;gray-500&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">text</span><span class="o">=</span><span class="s">&#34;Haseeb&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">hoverColor</span><span class="o">=</span><span class="s">&#34;blue-500&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">Logo</span>
</span></span><span class="line"><span class="cl">        <span class="na">accent</span><span class="o">=</span><span class="s">&#34;gray-500&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">color</span><span class="o">=</span><span class="s">&#34;gray-500&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">text</span><span class="o">=</span><span class="s">&#34;Haseeb&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">hoverColor</span><span class="o">=</span><span class="s">&#34;gray-500&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">Story</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">Preview</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">## Text
</span></span></span><span class="line"><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="cl">You must set the main text for the logo, such as a name using the <span class="sb">`text`</span> prop.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">Preview</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">Story</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;Main Text&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">Logo</span> <span class="na">text</span><span class="o">=</span><span class="s">&#34;Haseeb&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">Logo</span> <span class="na">text</span><span class="o">=</span><span class="s">&#34;John Smith&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">Logo</span> <span class="na">text</span><span class="o">=</span><span class="s">&#34;xyz.io&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">Logo</span> <span class="na">text</span><span class="o">=</span><span class="s">&#34;Random&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">Story</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">Preview</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">## Size
</span></span></span><span class="line"><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="cl">You can also adjust the size of the logo by passing the <span class="sb">`size`</span> prop.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">Preview</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">Story</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;Text Size&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">Logo</span> <span class="na">accent</span><span class="o">=</span><span class="s">&#34;gray-700&#34;</span> <span class="na">size</span><span class="o">=</span><span class="s">&#34;xs&#34;</span> <span class="na">text</span><span class="o">=</span><span class="s">&#34;Haseeb&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">Logo</span> <span class="na">accent</span><span class="o">=</span><span class="s">&#34;gray-700&#34;</span> <span class="na">size</span><span class="o">=</span><span class="s">&#34;sm&#34;</span> <span class="na">text</span><span class="o">=</span><span class="s">&#34;Haseeb&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">Logo</span> <span class="na">accent</span><span class="o">=</span><span class="s">&#34;gray-700&#34;</span> <span class="na">size</span><span class="o">=</span><span class="s">&#34;lg&#34;</span> <span class="na">text</span><span class="o">=</span><span class="s">&#34;Haseeb&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">Logo</span> <span class="na">accent</span><span class="o">=</span><span class="s">&#34;gray-700&#34;</span> <span class="na">size</span><span class="o">=</span><span class="s">&#34;xl&#34;</span> <span class="na">text</span><span class="o">=</span><span class="s">&#34;Haseeb&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">Logo</span> <span class="na">accent</span><span class="o">=</span><span class="s">&#34;gray-700&#34;</span> <span class="na">size</span><span class="o">=</span><span class="s">&#34;2xl&#34;</span> <span class="na">text</span><span class="o">=</span><span class="s">&#34;Haseeb&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">Logo</span> <span class="na">accent</span><span class="o">=</span><span class="s">&#34;gray-700&#34;</span> <span class="na">size</span><span class="o">=</span><span class="s">&#34;3xl&#34;</span> <span class="na">text</span><span class="o">=</span><span class="s">&#34;Haseeb&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">Logo</span> <span class="na">accent</span><span class="o">=</span><span class="s">&#34;gray-700&#34;</span> <span class="na">size</span><span class="o">=</span><span class="s">&#34;4xl&#34;</span> <span class="na">text</span><span class="o">=</span><span class="s">&#34;Haseeb&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">Story</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">Preview</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">## Dark Mode
</span></span></span><span class="line"><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="cl">Example dark mode component.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">Preview</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">Story</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;Dark Mode&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">{`bg-${theme.dark.background}</span> <span class="na">p-5</span><span class="err">`}</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">Logo</span>
</span></span><span class="line"><span class="cl">        <span class="na">accent</span><span class="o">=</span><span class="s">&#34;gray-500&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">color</span><span class="o">=</span><span class="s">{theme.dark.text}</span>
</span></span><span class="line"><span class="cl">        <span class="na">hoverColor</span><span class="o">=</span><span class="s">{theme.colors.primary}</span>
</span></span><span class="line"><span class="cl">        <span class="na">text</span><span class="o">=</span><span class="s">&#34;Haseeb&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">Story</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">Preview</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>In the diagram below is the <code>docs</code> tab in Storybook (default is the <code>canvas</code> tab). As you can see in the diagrams
below we have a table which is taken from the props in our Logo component. We also have the comments shown as
descriptions, any default values we assigned and the current value. Which we set above in the stories file
<code>Logo.stories.mdx</code>. We can then edit the values within the controls table, it will automatically re-render our
component on the fly. You can see this in the second image.</p>
<p><img
        loading="lazy"
        src="/posts/2020-07-20-how-to-use-storybooks-with-mdx/images/controls.png"
        type=""
        alt="Storybook Controls"
        
      /></p>
<p><img
        loading="lazy"
        src="/posts/2020-07-20-how-to-use-storybooks-with-mdx/images/controls2.png"
        type=""
        alt="Storybook Controls2"
        
      /></p>
<p>Then we can define our regular stories components like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl"><span class="gu">## Accent
</span></span></span><span class="line"><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="cl">You can adjust the accent (tags) color by passing the <span class="sb">`accent`</span> prop.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">Preview</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">Story</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;Accent Colour&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">Logo</span>
</span></span><span class="line"><span class="cl">        <span class="na">accent</span><span class="o">=</span><span class="s">&#34;gray-500&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">color</span><span class="o">=</span><span class="s">&#34;blue-500&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">text</span><span class="o">=</span><span class="s">&#34;Haseeb&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">hoverColor</span><span class="o">=</span><span class="s">&#34;blue-500&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">Logo</span>
</span></span><span class="line"><span class="cl">        <span class="na">accent</span><span class="o">=</span><span class="s">&#34;gray-500&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">color</span><span class="o">=</span><span class="s">&#34;black&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">text</span><span class="o">=</span><span class="s">&#34;Haseeb&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">hoverColor</span><span class="o">=</span><span class="s">&#34;blue-500&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">Story</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">Preview</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">## Colour
</span></span></span><span class="line"><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="cl">You can change the default colour of the logo by passing the <span class="sb">`color`</span> prop.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">Preview</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">Story</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;Main Text Colour&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">Logo</span>
</span></span><span class="line"><span class="cl">        <span class="na">accent</span><span class="o">=</span><span class="s">&#34;gray-500&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">color</span><span class="o">=</span><span class="s">&#34;gray-500&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">text</span><span class="o">=</span><span class="s">&#34;Haseeb&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">hoverColor</span><span class="o">=</span><span class="s">&#34;blue-500&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">Logo</span>
</span></span><span class="line"><span class="cl">        <span class="na">accent</span><span class="o">=</span><span class="s">&#34;gray-500&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">color</span><span class="o">=</span><span class="s">&#34;blue-500&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">text</span><span class="o">=</span><span class="s">&#34;Haseeb&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">hoverColor</span><span class="o">=</span><span class="s">&#34;blue-500&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">Story</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">Preview</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>The example above will be rendered into something shown in the image below. You can see from this example we are now
mixing our React component with our normal markdown syntax. This provides a very flexible way to document our components.
It allows others to interact with and look at an example of its use.</p>
<p><img
        loading="lazy"
        src="/posts/2020-07-20-how-to-use-storybooks-with-mdx/images/components.png"
        type=""
        alt="Storybook Component"
        
      /></p>
<p>That&rsquo;s about it documentation wise! Taking a look at the other features we added you can see the a11y issues in a panel
(either on the bottom or on the right of when you are in the <code>canvas</code> tab). You can see this in the diagram below.</p>
<p><img
        loading="lazy"
        src="/posts/2020-07-20-how-to-use-storybooks-with-mdx/images/a11y.png"
        type=""
        alt="A11y Panel"
        
      /></p>
<p>Then at the top of the screen, we just adjust the viewport and see how our component would look at different resolutions.
The default list we have includes resolutions for devices like various iPhones, Google Pixel and Galaxy Sx. You can
see this in the diagram below.</p>
<p><img
        loading="lazy"
        src="/posts/2020-07-20-how-to-use-storybooks-with-mdx/images/viewport.png"
        type=""
        alt="Viewport"
        
      /></p>
<h2 id="run">Run</h2>
<p>We can run the Storybook UI from our source code like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone https://gitlab.com/hmajid2301/articles.git
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> articles/30.<span class="se">\ </span>Storybooks,<span class="se">\ </span>Gatsby<span class="se">\ </span>and<span class="se">\ </span>MDX<span class="se">\ </span>II/source_code/
</span></span><span class="line"><span class="cl">yarn
</span></span><span class="line"><span class="cl">yarn storybook
</span></span></code></pre></div><p>With all of this setup now we can focus on a component first approach I like to use atomic design alongside Storybooks UI.
To create all of the components of my website before I start work on the pages themselves. This allows me to work out exactly
what each page will need, break them down into its core parts and work on them one at a time. Anyways that&rsquo;s it, thanks
for reading!</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/personal-site/-/tree/e415420744b2a8f49eddaf2d3058b23c70f46638/.storybook">Example Project</a></li>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2020-07-20-how-to-use-storybooks-with-mdx/source_code">Example source code</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to use Storybooks, Gatsby, Babel, Tailwind, Typescript together</title>
      <link>https://haseebmajid.dev/posts/2020-06-29-how-to-use-storybooks-gatsby-babel-tailwind-typescript-together/</link>
      <pubDate>Mon, 29 Jun 2020 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2020-06-29-how-to-use-storybooks-gatsby-babel-tailwind-typescript-together/</guid>
      <description>&lt;p&gt;Recently I started to re-design my website, I decided to use this as an opportunity to learn some new technologies
such as Gatsby, Tailwind. I also decided to try using Storybook. For this said project I used MDX to create my
Storybook stories. In this article, I will show you how you can create Storybooks stories, for a Gatsby project
with TailwindCSS, Typescript using MDX.&lt;/p&gt;
&lt;p&gt;You can find an example project using this &lt;a href=&#34;https://gitlab.com/hmajid2301/personal-site/-/tree/e415420744b2a8f49eddaf2d3058b23c70f46638/.storybook&#34;&gt;here&lt;/a&gt;.
You can also find a &lt;a href=&#34;https://storybook.haseebmajid.dev/&#34;&gt;demo site&lt;/a&gt; for said project.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Recently I started to re-design my website, I decided to use this as an opportunity to learn some new technologies
such as Gatsby, Tailwind. I also decided to try using Storybook. For this said project I used MDX to create my
Storybook stories. In this article, I will show you how you can create Storybooks stories, for a Gatsby project
with TailwindCSS, Typescript using MDX.</p>
<p>You can find an example project using this <a href="https://gitlab.com/hmajid2301/personal-site/-/tree/e415420744b2a8f49eddaf2d3058b23c70f46638/.storybook">here</a>.
You can also find a <a href="https://storybook.haseebmajid.dev/">demo site</a> for said project.</p>
<blockquote>
<p>This article assumes you already familiar with Typescript, TailwindCSS and Gatsby.</p>
</blockquote>
<h2 id="storybook">Storybook</h2>
<blockquote>
<p>Storybook is an open source tool for developing UI components in isolation for React, Vue, and Angular. It makes building stunning UIs organized and efficient. - Storybook Website</p>
</blockquote>
<p>Storybook allows us to create and test (visually) components in isolation. It can be a great way to both document all
of your components but also speed up development as all you need to focus on is one component at a time. Storybook
also has a ton of extra plugins/addons which can help to customise storybooks to your liking. One such example being
checking for any accessibility issues your components may have.</p>
<h3 id="mdx">MDX</h3>
<p>MDX is a combination of markdown mixed with JSX. It allows us to &ldquo;execute&rdquo; and &ldquo;render&rdquo; JSX code from within an MDX
document. When used with Storybook it means we get all of the flexibility of markdown. So we can use normal markdown
syntax, to document our component. We also get access to MDX-flavored Component Story Format (CSF) which includes a collection
of components called &ldquo;Doc Blocks&rdquo;, that allow Storybook to translate MDX files into storybook stories.</p>
<h2 id="setup">Setup</h2>
<p>OK let&rsquo;s go over what we need to do, first let&rsquo;s create our gatsby site by using the <code>gatsby-cli</code> tool.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">gatsby new gatsby-site
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> gatsby-site
</span></span></code></pre></div><h3 id="tailwindcss">TailwindCSS</h3>
<p>Now let&rsquo;s see how we add tailwindcss to this site:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">yarn add gatsby-plugin-typescript gatsby-plugin-postcss tailwindcss twin.macro postcss-preset-env
</span></span><span class="line"><span class="cl">vim gatsby-config.js
</span></span><span class="line"><span class="cl">vim postcss.config.js
</span></span><span class="line"><span class="cl">vim tailwind.config.js
</span></span><span class="line"><span class="cl">mkdir -p src/styles/
</span></span><span class="line"><span class="cl">vim src/styles/globals.css
</span></span><span class="line"><span class="cl">vim gatsby-browser.js
</span></span></code></pre></div><p>We need to update the <code>gatsby-config.js</code> file to add support for both typescript and PostCSS. Tailwind is written in PostCSS
so we need to include that in our gatsby file. You can either replace the default <code>gatsby-config.js</code> or update the plugins.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">plugins</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;gatsby-plugin-typescript&#34;</span><span class="p">,</span> <span class="s2">&#34;gatsby-plugin-postcss&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">plugins</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>Next we add a <code>postcss.config.js</code> file as per the Tailwind instructions found
<a href="https://tailwindcss.com/docs/installation#webpack-encore">here</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">tailwindcss</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;tailwindcss&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nx">plugins</span><span class="o">:</span> <span class="p">[</span><span class="nx">tailwindcss</span><span class="p">],</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><p>Finally, we create a <code>tailwind.config.js</code> file. Here we can add new colours, overwrite existing colours and extend the
configuration such as adding news fonts (<code>Inter</code>). This file will get merged with the default config by Tailwind.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">theme</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">themeVariants</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;dark&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nx">extend</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">colors</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">blue</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="mi">100</span><span class="o">:</span> <span class="s2">&#34;#EBF2FD&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="mi">200</span><span class="o">:</span> <span class="s2">&#34;#CDDFFA&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="mi">300</span><span class="o">:</span> <span class="s2">&#34;#AFCBF6&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="mi">400</span><span class="o">:</span> <span class="s2">&#34;#72A5F0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="mi">500</span><span class="o">:</span> <span class="s2">&#34;#367EE9&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="mi">600</span><span class="o">:</span> <span class="s2">&#34;#3171D2&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="mi">700</span><span class="o">:</span> <span class="s2">&#34;#204C8C&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="mi">800</span><span class="o">:</span> <span class="s2">&#34;#183969&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="mi">900</span><span class="o">:</span> <span class="s2">&#34;#102646&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="nx">monochrome</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="mi">900</span><span class="o">:</span> <span class="s2">&#34;#333&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="mi">800</span><span class="o">:</span> <span class="s2">&#34;#444&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="mi">700</span><span class="o">:</span> <span class="s2">&#34;#666&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="mi">600</span><span class="o">:</span> <span class="s2">&#34;#999&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="mi">500</span><span class="o">:</span> <span class="s2">&#34;#ddd&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="mi">400</span><span class="o">:</span> <span class="s2">&#34;#eee&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="mi">300</span><span class="o">:</span> <span class="s2">&#34;#f3f3f3&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="mi">200</span><span class="o">:</span> <span class="s2">&#34;#f8f8f8&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="mi">100</span><span class="o">:</span> <span class="s2">&#34;#fff&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="nx">fontFamily</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">header</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;Inter&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nx">variants</span><span class="o">:</span> <span class="p">{},</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>Next, to add the Tailwind styles or our app we need to create a CSS file, you can call this file whatever you want,
you just need to make sure it gets imported in such a place it can be used by any of your components.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="p">@</span><span class="k">tailwind</span> <span class="nt">base</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">@</span><span class="k">tailwind</span> <span class="nt">components</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">@</span><span class="k">tailwind</span> <span class="nt">utilities</span><span class="p">;</span>
</span></span></code></pre></div><p>One place we can import this is in the <code>gatsby-brower.js</code> file. It should be empty, add the import shown below.
We will add babel later on in the app, which will allow us to use imports in the style we&rsquo;ve just described.
In this example, we will use the <code>~</code> to mean <code>src</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="s2">&#34;~/styles/globals.css&#34;</span><span class="p">;</span>
</span></span></code></pre></div><h3 id="typescript">Typescript</h3>
<p>Now let&rsquo;s add typescript to our project:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">yarn add --dev react-docgen-typescript react-docgen-typescript-loader ts-loader typescript
</span></span><span class="line"><span class="cl">vim tsconfig.json
</span></span></code></pre></div><p>We will add some extra libraries that will be used by Storybooks to parse our Typescript components.
Like all Typescript projects, we need to include a <code>tsconfig.json</code> file. Note we add the <code>&quot;paths&quot;</code> so we can
have cleaner imports, this will be used alongside Babel.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;compileOnSave&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;compilerOptions&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;target&#34;</span><span class="p">:</span> <span class="s2">&#34;es5&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;module&#34;</span><span class="p">:</span> <span class="s2">&#34;es6&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;types&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;node&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;moduleResolution&#34;</span><span class="p">:</span> <span class="s2">&#34;node&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;esModuleInterop&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;lib&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;dom&#34;</span><span class="p">,</span> <span class="s2">&#34;es2015&#34;</span><span class="p">,</span> <span class="s2">&#34;es2017&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;jsx&#34;</span><span class="p">:</span> <span class="s2">&#34;react&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;sourceMap&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;strict&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;resolveJsonModule&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;noUnusedLocals&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;noImplicitAny&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;noUnusedParameters&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;noFallthroughCasesInSwitch&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;allowSyntheticDefaultImports&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;downlevelIteration&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;baseUrl&#34;</span><span class="p">:</span> <span class="s2">&#34;./&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;paths&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;~/*&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;src/*&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;include&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;./src/**/*&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;exclude&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;node_modules&#34;</span><span class="p">,</span> <span class="s2">&#34;plugins&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="babel">Babel</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">yarn add --dev babel-plugin-module-resolver babel-preset-gatsby babel-preset-react-app @babel/compat-data <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>@babel/core @babel/preset-env babel-loader
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">vim .babelrc
</span></span></code></pre></div><p><code>Gatsby</code> automatically uses Babel, however, to customise babel we need to create our own <code>.babelrc</code> file. You can read
more about it <a href="https://www.gatsbyjs.org/docs/babel/">here</a>. The main reason we want to use it is to allow use to have cleaner
imports. So we can use <code>~</code> instead of <code>src</code> in imports. So we can do <code>import &quot;~/styles/globals.css&quot;;</code> instead of
<code>import &quot;../../../styles/globals.css&quot;'</code>.</p>
<blockquote>
<p><a href="/blog/better-imports-with-babel-tspath/">You can read more about it here, I wrote a previous article on this topic.</a></p>
</blockquote>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;env&#34;</span><span class="p">:</span> <span class="p">{},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;plugins&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;module-resolver&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;root&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;./src&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;alias&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nt">&#34;~&#34;</span><span class="p">:</span> <span class="s2">&#34;./src&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;presets&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;babel-preset-gatsby&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;targets&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nt">&#34;browsers&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;&gt;0.25%&#34;</span><span class="p">,</span> <span class="s2">&#34;not dead&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="storybook-1">Storybook</h3>
<p>We will use the latest versions of Storybook (v6) so we can access the latest features. We will go over how we can use these features in the next article.</p>
<p>First remove any lines in your <code>package.json</code> that start with <code>@storybook</code>. In my case,
I removed <code>@storybook/addon-actions</code>, <code>@storybook/add-links</code>, <code>@storybook/addons</code> and
<code>@storybook/react</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">yarn add --dev @storybook/addon-docs@6.0.0-beta.20 @storybook/addon-essentials@6.0.0-beta.20 <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>@storybook/addon-storysource@6.0.0-beta.20  @storybook/preset-typescript@1.2.0 <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>@storybook/react@6.0.0-beta.20 core-js@2.6.5
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">npx -p @storybook/cli sb init -f
</span></span><span class="line"><span class="cl">vim .storybook/main.js
</span></span><span class="line"><span class="cl">vim .storybook/preview.js
</span></span><span class="line"><span class="cl">vim preview-head.html
</span></span><span class="line"><span class="cl">vim webpack.config.js
</span></span></code></pre></div><p>Next, we will update the <code>main.js</code> file. This will tell Storybook where to look for the stories, in this case in the <code>src</code> folder
any file called <code>x.stories.mdx</code> or <code>x.stories.tsx</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">stories</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;../src/**/*.stories.@(tsx|mdx)&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nx">addons</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;@storybook/addon-essentials&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;@storybook/addon-docs&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;@storybook/preset-typescript&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">],</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>Next, lets update the preview file. Here is typically you can define global parameters and decorators. Again
will see more of this in the next article.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span> <span class="nx">from</span> <span class="s2">&#34;react&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">action</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;@storybook/addon-actions&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">configure</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;@storybook/react&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="s2">&#34;../src/styles/globals.css&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="s2">&#34;./main.css&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">configure</span><span class="p">(</span><span class="nx">require</span><span class="p">.</span><span class="nx">context</span><span class="p">(</span><span class="s2">&#34;../src&#34;</span><span class="p">,</span> <span class="kc">true</span><span class="p">,</span> <span class="sr">/\.stories\.mdx$/</span><span class="p">),</span> <span class="nx">module</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">global</span><span class="p">.</span><span class="nx">___loader</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">enqueue</span><span class="o">:</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{},</span>
</span></span><span class="line"><span class="cl">  <span class="nx">hovering</span><span class="o">:</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{},</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="nx">global</span><span class="p">.</span><span class="nx">__PATH_PREFIX__</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="nb">window</span><span class="p">.</span><span class="nx">___navigate</span> <span class="o">=</span> <span class="p">(</span><span class="nx">pathname</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">action</span><span class="p">(</span><span class="s2">&#34;NavigateTo:&#34;</span><span class="p">)(</span><span class="nx">pathname</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>If we want to use any custom fonts, such as google fonts or other styles within our Tailwind, we need to
define them here.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">link</span>
</span></span><span class="line"><span class="cl">  <span class="na">href</span><span class="o">=</span><span class="s">&#34;https://fonts.googleapis.com/css2?family=Inter:wght@600,900&amp;display=swap&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="na">rel</span><span class="o">=</span><span class="s">&#34;stylesheet&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">/&gt;</span>
</span></span></code></pre></div><p>Storybook uses webpack, so if we want to add extra webpack options, we do that here. This allows us to use
things like Babel and PostCSS loader.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">config</span> <span class="p">})</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">config</span><span class="p">.</span><span class="nx">module</span><span class="p">.</span><span class="nx">rules</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">use</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">loader</span> <span class="o">=</span> <span class="nx">require</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="s2">&#34;babel-loader&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nx">config</span><span class="p">.</span><span class="nx">module</span><span class="p">.</span><span class="nx">rules</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">use</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">options</span><span class="p">.</span><span class="nx">presets</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="nx">require</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="s2">&#34;@babel/preset-react&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nx">require</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="s2">&#34;@babel/preset-env&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">config</span><span class="p">.</span><span class="nx">module</span><span class="p">.</span><span class="nx">rules</span><span class="p">.</span><span class="nx">push</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nx">test</span><span class="o">:</span> <span class="sr">/\.(ts|tsx)$/</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">loader</span><span class="o">:</span> <span class="nx">require</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="s2">&#34;babel-loader&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nx">options</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">presets</span><span class="o">:</span> <span class="p">[[</span><span class="s2">&#34;react-app&#34;</span><span class="p">,</span> <span class="p">{</span> <span class="nx">flow</span><span class="o">:</span> <span class="kc">false</span><span class="p">,</span> <span class="nx">typescript</span><span class="o">:</span> <span class="kc">true</span> <span class="p">}]],</span>
</span></span><span class="line"><span class="cl">      <span class="nx">plugins</span><span class="o">:</span> <span class="p">[],</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">config</span><span class="p">.</span><span class="nx">module</span><span class="p">.</span><span class="nx">rules</span><span class="p">.</span><span class="nx">push</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nx">test</span><span class="o">:</span> <span class="sr">/\.css$/</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">use</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">loader</span><span class="o">:</span> <span class="s2">&#34;postcss-loader&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">options</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nx">sourceMap</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="nx">config</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">path</span><span class="o">:</span> <span class="s2">&#34;./.storybook/&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="nx">config</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><h3 id="component">Component</h3>
<p>Finally, let&rsquo;s create a component that we will create a story for. First, create a new folder at <code>src/components/Logo</code>.
In that folder let&rsquo;s create the following files:</p>
<blockquote>
<p>Note the comments in the Props will be the comments shown in our story later, if you use the correct addons for Storybook. We will go over this in the next article.</p>
</blockquote>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span> <span class="kr">from</span> <span class="s1">&#39;react&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">tw</span> <span class="kr">from</span> <span class="s1">&#39;twin.macro&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">interface</span> <span class="nx">Props</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/** The colour of the opening and closing tags. */</span>
</span></span><span class="line"><span class="cl">  <span class="nx">accent?</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/** The colour of main text. */</span>
</span></span><span class="line"><span class="cl">  <span class="nx">color?</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/** The colour when you hover over the logo. */</span>
</span></span><span class="line"><span class="cl">  <span class="nx">hoverColor?</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/** The main text of the logo for example, your name. */</span>
</span></span><span class="line"><span class="cl">  <span class="nx">text</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/** The size of the main text  */</span>
</span></span><span class="line"><span class="cl">  <span class="nx">size</span><span class="o">?:</span> <span class="s1">&#39;xs&#39;</span> <span class="o">|</span> <span class="s1">&#39;sm&#39;</span> <span class="o">|</span> <span class="s1">&#39;lg&#39;</span> <span class="o">|</span> <span class="s1">&#39;xl&#39;</span> <span class="o">|</span> <span class="s1">&#39;2xl&#39;</span> <span class="o">|</span> <span class="s1">&#39;3xl&#39;</span> <span class="o">|</span> <span class="s1">&#39;4xl&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">Logo</span> <span class="o">=</span> <span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nx">accent</span> <span class="o">=</span> <span class="s1">&#39;black&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">color</span> <span class="o">=</span> <span class="s1">&#39;black&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">hoverColor</span> <span class="o">=</span> <span class="s1">&#39;blue-500&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">text</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">size</span> <span class="o">=</span> <span class="s1">&#39;2xl&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span><span class="o">:</span> <span class="nx">Props</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">LogoContainer</span>
</span></span><span class="line"><span class="cl">    <span class="na">className</span><span class="o">=</span><span class="p">{</span><span class="sb">`hover:text-</span><span class="si">${</span><span class="nx">hoverColor</span><span class="si">}</span><span class="sb"> text-</span><span class="si">${</span><span class="nx">color</span><span class="si">}</span><span class="sb"> lg:text-</span><span class="si">${</span><span class="nx">size</span><span class="si">}</span><span class="sb">
</span></span></span><span class="line"><span class="cl"><span class="sb">    md:text-xl sm:text-md text-sm`</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">Tag</span> <span class="na">className</span><span class="o">=</span><span class="p">{</span><span class="sb">`text-</span><span class="si">${</span><span class="nx">accent</span><span class="si">}</span><span class="sb">`</span><span class="p">}</span> <span class="na">data-testid</span><span class="o">=</span><span class="s">&#34;OpeningTag&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="o">&amp;</span><span class="nx">lt</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">Tag</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="nx">text</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">Tag</span> <span class="na">className</span><span class="o">=</span><span class="p">{</span><span class="sb">`text-</span><span class="si">${</span><span class="nx">accent</span><span class="si">}</span><span class="sb">`</span><span class="p">}</span> <span class="na">data-testid</span><span class="o">=</span><span class="s">&#34;ClosingTag&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="o">/&amp;</span><span class="nx">gt</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">Tag</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">LogoContainer</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">LogoContainer</span> <span class="o">=</span> <span class="nx">tw</span><span class="p">.</span><span class="nx">div</span><span class="sb">`cursor-pointer font-header font-black tracking-wide `</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">Tag</span> <span class="o">=</span> <span class="nx">tw</span><span class="p">.</span><span class="nx">span</span><span class="sb">``</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">Logo</span><span class="p">;</span>
</span></span></code></pre></div><p>This index file makes it easier to import the component from other files. As we don&rsquo;t have to do
<code>import {Logo} from &quot;src/components/Logo/Logo.ts</code> we can use <code>import {Logo} from &quot;src/components/Logo</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">export</span> <span class="p">{</span> <span class="k">default</span> <span class="kr">as</span> <span class="nx">Logo</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;./Logo&#39;</span><span class="p">;</span>
</span></span></code></pre></div><h4 id="storybook-2">Storybook</h4>
<p>Now we have set everything up but do we create a story for our component. First, create a new file at <code>src/components/Logo/Logo.stories.mdx</code>.
You could keep this in another folder like storybooks/ or keep it in the same folder as your component, it&rsquo;s all personal preference.
Some people will also have all unit tests in the same folder <code>src/components/Logo/</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl">import { Meta, Story, Preview, Props } from &#34;@storybook/addon-docs/blocks&#34;;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">import Logo from &#34;./Logo&#34;;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">Meta</span> <span class="na">title</span><span class="o">=</span><span class="s">&#34;Logo&#34;</span> <span class="na">component</span><span class="o">=</span><span class="s">{Logo}</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gh"># Logo
</span></span></span><span class="line"><span class="cl"><span class="gh"></span>
</span></span><span class="line"><span class="cl"><span class="gu">## Accent
</span></span></span><span class="line"><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="cl">You can adjust the accent (tags) color by passing the <span class="sb">`accent`</span> prop.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">Preview</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">Story</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;Accent Colour&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">Logo</span> <span class="na">accent</span><span class="o">=</span><span class="s">&#34;gray-500&#34;</span> <span class="na">color</span><span class="o">=</span><span class="s">&#34;blue-500&#34;</span> <span class="na">text</span><span class="o">=</span><span class="s">&#34;Haseeb&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">Logo</span> <span class="na">accent</span><span class="o">=</span><span class="s">&#34;gray-500&#34;</span> <span class="na">color</span><span class="o">=</span><span class="s">&#34;black&#34;</span> <span class="na">text</span><span class="o">=</span><span class="s">&#34;Haseeb&#34;</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">Story</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">Preview</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>Add the following to your <code>package.json</code> to the &ldquo;scripts&rdquo; section. We need to pass it the <code>NODE_ENV=test</code>
environment variable, else the Gatsby Babel plugin will complain.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="s2">&#34;storybook&#34;</span><span class="err">:</span> <span class="s2">&#34;NODE_ENV=test start-storybook -p 6006&#34;</span><span class="err">,</span>
</span></span><span class="line"><span class="cl"><span class="s2">&#34;build-storybook&#34;</span><span class="err">:</span> <span class="s2">&#34;NODE_ENV=test build-storybook&#34;</span>
</span></span></code></pre></div><p>Now we can run our Storybook by running the following command:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">yarn storybook
</span></span></code></pre></div><p>That&rsquo;s it! We managed to get Storybook to work with Gatsby. Where Gatsby is using Tailwind, Babel and Typescript.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/personal-site/-/tree/e415420744b2a8f49eddaf2d3058b23c70f46638/.storybook">Example Project</a></li>
<li><a href="https://storybook.haseebmajid.dev/">Example Storybook</a></li>
<li>Cover image from, <a href="https://worldvectorlogo.com/downloaded/storybook-1">World Vector Logo</a></li>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2020-06-29-how-to-use-storybooks-gatsby-babel-tailwind-typescript-together/source_code">Example source code</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to use Gitlab CI, Pytest and docker-compose together</title>
      <link>https://haseebmajid.dev/posts/2020-06-22-how-to-use-gitlab-ci-pytest-and-docker-compose-together/</link>
      <pubDate>Mon, 22 Jun 2020 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2020-06-22-how-to-use-gitlab-ci-pytest-and-docker-compose-together/</guid>
      <description>&lt;p&gt;On a recent project, I was working on, I wanted to test my web service using docker-compose where I can run and kill
Docker containers used by the application and see how my web application reacts to that. In this article, we will
go over how you start docker containers using docker-compose from within Gitlab CI.&lt;/p&gt;
&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2020-06-22-how-to-use-gitlab-ci-pytest-and-docker-compose-together/images/main.png&#34;
        type=&#34;&#34;
        alt=&#34;main&#34;
        
      /&gt;&lt;/p&gt;
&lt;p&gt;The diagram above is a visualisation of what we are trying to achieve. We want to spawn Docker containers using docker-compose
from within our job. The spawning and destruction of these Docker containers will be done via our Python code. We can achieve
this by using dind (Docker in Docker). I have written a previous article on this topic which you can read more about
&lt;a href=&#34;https://haseebmajid.dev/posts/2020-05-01-how-to-use-dind-with-gitlab-ci/&#34;&gt;here&lt;/a&gt;. This article assumes you already somewhat familiar
with Docker, docker-compose and Pytest.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>On a recent project, I was working on, I wanted to test my web service using docker-compose where I can run and kill
Docker containers used by the application and see how my web application reacts to that. In this article, we will
go over how you start docker containers using docker-compose from within Gitlab CI.</p>
<p><img
        loading="lazy"
        src="/posts/2020-06-22-how-to-use-gitlab-ci-pytest-and-docker-compose-together/images/main.png"
        type=""
        alt="main"
        
      /></p>
<p>The diagram above is a visualisation of what we are trying to achieve. We want to spawn Docker containers using docker-compose
from within our job. The spawning and destruction of these Docker containers will be done via our Python code. We can achieve
this by using dind (Docker in Docker). I have written a previous article on this topic which you can read more about
<a href="/posts/2020-05-01-how-to-use-dind-with-gitlab-ci/">here</a>. This article assumes you already somewhat familiar
with Docker, docker-compose and Pytest.</p>
<p>This compose file will be used to start our Docker containers.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;3&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">service1</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">container1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">docker</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;tail&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;-f&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;/dev/null&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">service2</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">container2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">docker</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;tail&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;-f&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;/dev/null&#34;</span><span class="p">]</span><span class="w">
</span></span></span></code></pre></div><h2 id="gitlab-ci">Gitlab CI</h2>
<p>Next, we have our <code>.gitlab-ci.yml</code> file, this file is used to tell Gitlab CI what our CI jobs should do.
In this example, we have one job called <code>test:integration</code> which will run our integration tests. But before we do that
we need a way to access the Docker daemon from within Gitlab CI, this can be done by using the <code>docker:dind</code> service.</p>
<p>The docker:dind image automatically using its entry point starts a docker daemon. We need to use this daemon to
start/stop our Docker images within CI. The docker:dind (dind = Docker in Docker) image is almost identical to
the docker image. The difference being the dind image starts a Docker daemon. In this example, the job will
use the docker image as the client and connect to the daemon running in this container.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">docker:dind</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">tests:integration</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">hmajid2301/dind-docker-compose</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">pip install pytest docker lovely-pytest-docker</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">pytest -s tests/test_integration.py</span><span class="w">
</span></span></span></code></pre></div><p>The job itself is very simple, it uses a container which already comes with <code>docker</code> and <code>docker-compose</code>. Next we
install the dependencies we need for our tests. Then it runs our tests.</p>
<h2 id="tests">Tests</h2>
<p>Now onto our actual tests file. It looks more complicated than it is:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">docker</span> <span class="k">as</span> <span class="nn">docker_py</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pytest</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">docker_client</span> <span class="o">=</span> <span class="n">docker_py</span><span class="o">.</span><span class="n">from_env</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">docker_compose</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span><span class="p">(</span><span class="n">scope</span><span class="o">=</span><span class="s2">&#34;session&#34;</span><span class="p">,</span> <span class="n">autouse</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">docker</span><span class="p">(</span><span class="n">docker_services</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">global</span> <span class="n">docker_compose</span>
</span></span><span class="line"><span class="cl">    <span class="n">docker_compose</span> <span class="o">=</span> <span class="n">docker_services</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span><span class="p">(</span><span class="n">scope</span><span class="o">=</span><span class="s2">&#34;session&#34;</span><span class="p">,</span> <span class="n">autouse</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">setup</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">docker_compose</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">yield</span>
</span></span><span class="line"><span class="cl">    <span class="n">docker_compose</span><span class="o">.</span><span class="n">shutdown</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">kill_container</span><span class="p">(</span><span class="n">container_name</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">container</span> <span class="o">=</span> <span class="n">get_container</span><span class="p">(</span><span class="n">container_name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">container</span><span class="o">.</span><span class="n">kill</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">container</span><span class="o">.</span><span class="n">remove</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_container</span><span class="p">(</span><span class="n">container_name</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">containers</span> <span class="o">=</span> <span class="n">docker_client</span><span class="o">.</span><span class="n">containers</span><span class="o">.</span><span class="n">list</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">container</span> <span class="ow">in</span> <span class="n">containers</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">container</span><span class="o">.</span><span class="n">name</span> <span class="o">==</span> <span class="n">container_name</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">container</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">start_container</span><span class="p">(</span><span class="n">service_name</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">docker_compose</span><span class="o">.</span><span class="n">start</span><span class="p">(</span><span class="n">service_name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">test_two_containers</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">containers</span> <span class="o">=</span> <span class="n">docker_client</span><span class="o">.</span><span class="n">containers</span><span class="o">.</span><span class="n">list</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">containers</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">test_kill_container1</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">kill_container</span><span class="p">(</span><span class="s2">&#34;container1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">containers</span> <span class="o">=</span> <span class="n">docker_client</span><span class="o">.</span><span class="n">containers</span><span class="o">.</span><span class="n">list</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">container1</span> <span class="o">=</span> <span class="n">get_container</span><span class="p">(</span><span class="s2">&#34;container1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">containers</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="ow">not</span> <span class="n">container1</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">test_start_container1</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">start_container</span><span class="p">(</span><span class="s2">&#34;service1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">containers</span> <span class="o">=</span> <span class="n">docker_client</span><span class="o">.</span><span class="n">containers</span><span class="o">.</span><span class="n">list</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">container1</span> <span class="o">=</span> <span class="n">get_container</span><span class="p">(</span><span class="s2">&#34;container1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">containers</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="n">container11</span>
</span></span></code></pre></div><p>The first part is the setup, we will use the python Docker library,<code>docker</code>. Which allow us to use Python code to
control our Docker daemon. The first <code>@pytest-fixture</code> called <code>docker</code> allows us to use the
<code>lovely-pytest-docker</code> library. To give us a <code>docker_compose</code> object which will allow us to again use Python code to
control our <code>docker-compose.yml</code> file (start-up/stop containers). The library also has some very nice features such
as waiting for containers or executing commands within containers. You can find the available functions
<a href="https://github.com/lovelysystems/lovely-pytest-docker/blob/master/src/lovely/pytest/docker/compose.py">here</a>.
Now we can access the <code>docker_compose</code> object by using our <code>docker_compose</code> global variable.</p>
<p>The reason we have both a library for Docker and docker-compose is because at the moment there is no way to use
<code>lovely-pytest-docker</code> (as far as I&rsquo;m aware) to stop a single container. So we need to use the standard <code>docker</code>
library to do that. We also use the standard <code>docker</code> library to find out if a container is running.</p>
<p>Next, we have the <code>setup()</code> fixture which we auto use, this means the fixture is run before our test, normally a fixture
would only be called once it has been referred to within another function. In this function, we start both of our containers
in our <code>docker-compose</code> file. This is the same as running <code>docker-compose up --build -d</code>. Next we <code>yield</code>, how exactly the
<code>yield</code> command works I won&rsquo;t go over in this article, all you have to know is that everything after the yield will only
be run after all of our tests. In this case we teardown our containers (stop them). This is the same as running <code>docker-compose down</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">docker</span> <span class="k">as</span> <span class="nn">docker_py</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pytest</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">docker_client</span> <span class="o">=</span> <span class="n">docker_py</span><span class="o">.</span><span class="n">from_env</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">docker_compose</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span><span class="p">(</span><span class="n">scope</span><span class="o">=</span><span class="s2">&#34;session&#34;</span><span class="p">,</span> <span class="n">autouse</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">docker</span><span class="p">(</span><span class="n">docker_services</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">global</span> <span class="n">docker_compose</span>
</span></span><span class="line"><span class="cl">    <span class="n">docker_compose</span> <span class="o">=</span> <span class="n">docker_services</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span><span class="p">(</span><span class="n">scope</span><span class="o">=</span><span class="s2">&#34;session&#34;</span><span class="p">,</span> <span class="n">autouse</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">setup</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">docker_compose</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">yield</span>
</span></span><span class="line"><span class="cl">    <span class="n">docker_compose</span><span class="o">.</span><span class="n">shutdown</span><span class="p">()</span>
</span></span></code></pre></div><p>The next part of our file contains some helper functions I&rsquo;ve written. These functions can be used by multiple tests.
This helps our tests file stay more DRY (do not repeat yourself). You may well want to make these part of a
(helper) class that you expose as a fixture. If you wanted to structure these properly so they can be accessed by
more than one file. Also whilst we are on this topic, we may want to move our fixture to <code>conftest.py</code>, again to allow
other files to use the same fixture we have defined here. But to keep this example simpler we will leave it here.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">kill_container</span><span class="p">(</span><span class="n">container_name</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">container</span> <span class="o">=</span> <span class="n">get_container</span><span class="p">(</span><span class="n">container_name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">container</span><span class="o">.</span><span class="n">kill</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">container</span><span class="o">.</span><span class="n">remove</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_container</span><span class="p">(</span><span class="n">container_name</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">containers</span> <span class="o">=</span> <span class="n">docker_client</span><span class="o">.</span><span class="n">containers</span><span class="o">.</span><span class="n">list</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">container</span> <span class="ow">in</span> <span class="n">containers</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">container</span><span class="o">.</span><span class="n">name</span> <span class="o">==</span> <span class="n">container_name</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">container</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">start_container</span><span class="p">(</span><span class="n">service_name</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">docker_compose</span><span class="o">.</span><span class="n">start</span><span class="p">(</span><span class="n">service_name</span><span class="p">)</span>
</span></span></code></pre></div><p>Finally, onto our actual tests, in reality, these tests are very boring and not super useful but they should give you an
idea what you can do. Such as killing the database container and then check how your Python application responds. Then
you can start the database container and again check how your Python application responds.
You can read more about how you can test Python Flask applications with pytest
<a href="https://medium.com/@hmajid2301/testing-mocking-a-connexion-flask-application-with-pytest-bacfd07099eb">here</a>.</p>
<p>So what do our tests do? Well, the first one checks the number of containers running is equal to 2.
The next one kills <code>container1</code> and checks that only one container is running and it&rsquo;s not <code>container1</code>.
Our final test starts <code>container1</code> and checks that it is running and the number of containers running
is back to 2. After this final test has completed then the <code>setup</code> fixture will run its <code>docker_compose.shutdown()</code>
command.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">test_two_containers</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">containers</span> <span class="o">=</span> <span class="n">docker_client</span><span class="o">.</span><span class="n">containers</span><span class="o">.</span><span class="n">list</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">containers</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">test_kill_container1</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">kill_container</span><span class="p">(</span><span class="s2">&#34;container1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">containers</span> <span class="o">=</span> <span class="n">docker_client</span><span class="o">.</span><span class="n">containers</span><span class="o">.</span><span class="n">list</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">container1</span> <span class="o">=</span> <span class="n">get_container</span><span class="p">(</span><span class="s2">&#34;container1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">containers</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="ow">not</span> <span class="n">container1</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">test_start_container1</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">start_container</span><span class="p">(</span><span class="s2">&#34;service&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">containers</span> <span class="o">=</span> <span class="n">docker_client</span><span class="o">.</span><span class="n">containers</span><span class="o">.</span><span class="n">list</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">container1</span> <span class="o">=</span> <span class="n">get_container</span><span class="p">(</span><span class="s2">&#34;container1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">containers</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="n">container1</span>
</span></span></code></pre></div><p>That&rsquo;s it we&rsquo;ve managed to start/stop Docker containers from within Gitlab CI, using DinD. In a future article I will
explain how you could run your tests within a Docker container you&rsquo;ve started. Say you had three containers <code>nginx</code>, <code>flask</code>
and <code>postgres</code> and you wanted to run your tests within the <code>flask</code> container. But for now that&rsquo;s it, thanks for reading!</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2020-06-22-how-to-use-gitlab-ci-pytest-and-docker-compose-together/source_code">Example source code</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Testing a Connexion/Flask Application with Pytest</title>
      <link>https://haseebmajid.dev/posts/2020-06-09-testing-a-connexion-flask-application-with-pytest/</link>
      <pubDate>Tue, 09 Jun 2020 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2020-06-09-testing-a-connexion-flask-application-with-pytest/</guid>
      <description>&lt;p&gt;In this article, I will show you how you can test a Python web service that was built using &lt;a href=&#34;https://github.com/zalando/connexion/&#34;&gt;Connexion&lt;/a&gt;
(a wrapper library around Flask). We will go over how you can mock functions and how you can test
your endpoints. There are two related articles I have written in the past listed below. In the first
one we go over how to create a web service using Connexions, the same web service we will in this article.
In the second article I introduce how you can use &lt;code&gt;pytest-mock&lt;/code&gt; and &lt;code&gt;pytest-flask&lt;/code&gt; to test a Flask web
service.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this article, I will show you how you can test a Python web service that was built using <a href="https://github.com/zalando/connexion/">Connexion</a>
(a wrapper library around Flask). We will go over how you can mock functions and how you can test
your endpoints. There are two related articles I have written in the past listed below. In the first
one we go over how to create a web service using Connexions, the same web service we will in this article.
In the second article I introduce how you can use <code>pytest-mock</code> and <code>pytest-flask</code> to test a Flask web
service.</p>
<ul>
<li><a href="/blog/rest-api-openapi-flask-connexion/">Implementing a Simple REST API using OpenAPI, Flask &amp; Connexions</a></li>
<li><a href="/blog/testing-with-pytest-mock-and-pytest-flask/">Testing with pytest-mock and pytest-flask</a></li>
</ul>
<p>The example app we will be writing tests for is a very simple CRUD API managing a pet store. It allows us
to add pets, remove pets, update pets and query pets we have in the store.</p>
<h2 id="structure">Structure</h2>
<p>You can find the source code here. Our project structure looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">.
</span></span><span class="line"><span class="cl">├── openapi
</span></span><span class="line"><span class="cl">│   └── specification.yml
</span></span><span class="line"><span class="cl">├── requirements.txt
</span></span><span class="line"><span class="cl">├── test_api
</span></span><span class="line"><span class="cl">│   ├── core
</span></span><span class="line"><span class="cl">│   │   ├── __init__.py
</span></span><span class="line"><span class="cl">│   │   ├── pets.json
</span></span><span class="line"><span class="cl">│   │   └── pets.py
</span></span><span class="line"><span class="cl">│   ├── __init__.py
</span></span><span class="line"><span class="cl">│   ├── run.py
</span></span><span class="line"><span class="cl">│   └── web
</span></span><span class="line"><span class="cl">│       ├── controllers
</span></span><span class="line"><span class="cl">│       │   ├── __init__.py
</span></span><span class="line"><span class="cl">│       │   └── pets_controller.py
</span></span><span class="line"><span class="cl">│       ├── encoder.py
</span></span><span class="line"><span class="cl">│       ├── __init__.py
</span></span><span class="line"><span class="cl">│       ├── models
</span></span><span class="line"><span class="cl">│       │   ├── base_model_.py
</span></span><span class="line"><span class="cl">│       │   ├── __init__.py
</span></span><span class="line"><span class="cl">│       │   ├── pet.py
</span></span><span class="line"><span class="cl">│       │   └── pets.py
</span></span><span class="line"><span class="cl">│       └── util.py
</span></span><span class="line"><span class="cl">└── tests
</span></span><span class="line"><span class="cl">    ├── conftest.py
</span></span><span class="line"><span class="cl">    ├── __init__.py
</span></span><span class="line"><span class="cl">    └── test_pets_controller.py
</span></span></code></pre></div><h2 id="api">API</h2>
<p>Here is our controller module called <code>web/controller/pets_controller.py</code>. This is where connexion routes are requests to:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">connexion</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">six</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">..models.pet</span> <span class="kn">import</span> <span class="n">Pet</span>  <span class="c1"># noqa: E501</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">..models.pets</span> <span class="kn">import</span> <span class="n">Pets</span>  <span class="c1"># noqa: E501</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">..</span> <span class="kn">import</span> <span class="n">util</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">test_api.core</span> <span class="kn">import</span> <span class="n">pets</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_pet</span><span class="p">(</span><span class="n">pet_id</span><span class="p">):</span>  <span class="c1"># noqa: E501</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Get a pet in the store
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">     # noqa: E501
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">    :param pet_id: The id of the pet to retrieve
</span></span></span><span class="line"><span class="cl"><span class="s2">    :type pet_id: str
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">    :rtype: Pet
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">pet</span> <span class="o">=</span> <span class="n">pets</span><span class="o">.</span><span class="n">get_pet</span><span class="p">(</span><span class="n">pet_id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">response</span> <span class="o">=</span> <span class="n">Pet</span><span class="p">(</span><span class="nb">id</span><span class="o">=</span><span class="n">pet</span><span class="o">.</span><span class="n">id</span><span class="p">,</span> <span class="n">breed</span><span class="o">=</span><span class="n">pet</span><span class="o">.</span><span class="n">breed</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="n">pet</span><span class="o">.</span><span class="n">name</span><span class="p">,</span> <span class="n">price</span><span class="o">=</span><span class="n">pet</span><span class="o">.</span><span class="n">price</span><span class="p">),</span> <span class="mi">200</span>
</span></span><span class="line"><span class="cl">    <span class="k">except</span> <span class="ne">KeyError</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">response</span> <span class="o">=</span> <span class="p">{},</span> <span class="mi">404</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">response</span>
</span></span></code></pre></div><p>Connexion uses the open API specification <code>openapi/specification.yml</code>, to work out which function to route requests
for the path <code>/pet/{pet_id}</code>. It uses the <code>operationId</code> alongside the <code>x-swagger-router-controller</code> to determine
the function to call in the <code>pets_controller.py</code> module.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="l">/pet/{pet_id}:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">get</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">tags</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;pet&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">summary</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Get a pet in the store&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">operationId</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;get_pet&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">parameters</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;pet_id&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">in</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;path&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;The id of the pet to retrieve&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">required</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;string&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">responses</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">200</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Successfully retrived pet&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">schema</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">$ref</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#/definitions/Pet&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">404</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Pet doesn&#39;t exist&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">x-swagger-router-controller</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;test_api.web.controllers.pets_controller&#34;</span><span class="w">
</span></span></span></code></pre></div><h2 id="tests">Tests</h2>
<p>Now onto our tests!</p>
<h3 id="libraries">Libraries</h3>
<p><em>pytest-flask</em> allows us to specify an app fixture and then send API requests with this app. Usage is similar to the <code>requests</code> library when sending HTTP requests to our app.</p>
<p><em>pytest-mock</em> is a simple wrapper around the unit test mock library, so anything you can do using <code>unittest.mock</code> you can do with <code>pytest-mock</code>. The main difference in usage is you can access it using a fixture <code>mocker</code>, also the mock ends at the end of the test. Whereas with the normal mock library if you say mock the <code>open()</code> function, it will be mocked for the remaining duration of that test module, i.e. it will affect other tests.</p>
<h3 id="conftestpy">conftest.py</h3>
<p>The <code>conftest.py</code> file is automatically run by pytest and allows our test modules to access fixtures defined
in this file. One of the best features of Pytest is fixtures. Fixture are functions that have re-usable bits of code we
can run in our unit tests, such as static data used by tests.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">json</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pytest</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">test_api.run</span> <span class="kn">import</span> <span class="n">create_app</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span><span class="p">(</span><span class="n">scope</span><span class="o">=</span><span class="s2">&#34;session&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">app</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">abs_file_path</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">abspath</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="vm">__file__</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="n">openapi_path</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">abs_file_path</span><span class="p">,</span> <span class="s2">&#34;../&#34;</span><span class="p">,</span> <span class="s2">&#34;openapi&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s2">&#34;SPEC_PATH&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">openapi_path</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">app</span> <span class="o">=</span> <span class="n">create_app</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">app</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span><span class="p">(</span><span class="n">scope</span><span class="o">=</span><span class="s2">&#34;session&#34;</span><span class="p">,</span> <span class="n">autouse</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">clean_up</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="k">yield</span>
</span></span><span class="line"><span class="cl">    <span class="n">default_pets</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;1&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;ginger&#34;</span><span class="p">,</span> <span class="s2">&#34;breed&#34;</span><span class="p">:</span> <span class="s2">&#34;bengal&#34;</span><span class="p">,</span> <span class="s2">&#34;price&#34;</span><span class="p">:</span> <span class="mi">100</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;2&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;sam&#34;</span><span class="p">,</span> <span class="s2">&#34;breed&#34;</span><span class="p">:</span> <span class="s2">&#34;husky&#34;</span><span class="p">,</span> <span class="s2">&#34;price&#34;</span><span class="p">:</span> <span class="mi">10</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;3&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;guido&#34;</span><span class="p">,</span> <span class="s2">&#34;breed&#34;</span><span class="p">:</span> <span class="s2">&#34;python&#34;</span><span class="p">,</span> <span class="s2">&#34;price&#34;</span><span class="p">:</span> <span class="mi">518</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">abs_file_path</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">abspath</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="vm">__file__</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="n">json_path</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">abs_file_path</span><span class="p">,</span> <span class="s2">&#34;../&#34;</span><span class="p">,</span> <span class="s2">&#34;test_api&#34;</span><span class="p">,</span> <span class="s2">&#34;core&#34;</span><span class="p">,</span> <span class="s2">&#34;pets.json&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">json_path</span><span class="p">,</span> <span class="s2">&#34;w&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">pet_store</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">json</span><span class="o">.</span><span class="n">dump</span><span class="p">(</span><span class="n">default_pets</span><span class="p">,</span> <span class="n">pet_store</span><span class="p">,</span> <span class="n">indent</span><span class="o">=</span><span class="mi">4</span><span class="p">)</span>
</span></span></code></pre></div><h4 id="app">app()</h4>
<p>In this file, we have two functions: the <code>app</code> allows users to pass the <code>client</code> argument to other tests
and then we can test our web application. You can get more information
<a href="https://flask.palletsprojects.com/en/1.1.x/testing/#the-testing-skeleton">here</a> about how Flask apps can be tested.
Essentially we don&rsquo;t need to start/stop a server before/after our tests.</p>
<p>By giving it the <code>scope=session</code> the fixture will be created once before all of our tests run. Our <code>run.py</code> file looks
like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">connexion</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">.web</span> <span class="kn">import</span> <span class="n">encoder</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">create_app</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="s2">&#34;SPEC_PATH&#34;</span> <span class="ow">in</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">openapi_path</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s2">&#34;SPEC_PATH&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">abs_file_path</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">abspath</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="vm">__file__</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="n">openapi_path</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">abs_file_path</span><span class="p">,</span> <span class="s2">&#34;../&#34;</span><span class="p">,</span> <span class="s2">&#34;../&#34;</span><span class="p">,</span> <span class="s2">&#34;openapi&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">app</span> <span class="o">=</span> <span class="n">connexion</span><span class="o">.</span><span class="n">FlaskApp</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="vm">__name__</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">specification_dir</span><span class="o">=</span><span class="n">openapi_path</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">options</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;swagger_ui&#34;</span><span class="p">:</span> <span class="kc">False</span><span class="p">,</span> <span class="s2">&#34;serve_spec&#34;</span><span class="p">:</span> <span class="kc">False</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">app</span><span class="o">.</span><span class="n">add_api</span><span class="p">(</span><span class="s2">&#34;specification.yml&#34;</span><span class="p">,</span> <span class="n">strict_validation</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">flask_app</span> <span class="o">=</span> <span class="n">app</span><span class="o">.</span><span class="n">app</span>
</span></span><span class="line"><span class="cl">    <span class="n">flask_app</span><span class="o">.</span><span class="n">json_encoder</span> <span class="o">=</span> <span class="n">encoder</span><span class="o">.</span><span class="n">JSONEncoder</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">flask_app</span>
</span></span></code></pre></div><p>The <code>create_app</code> function creates our web application and returns a Flask object. Remember the Connexion library is
just a wrapper around Flask. Connexion just reduces the boilerplate code we wrote. Again you can have a read of the
article above to get more details about how it works.</p>
<h4 id="clean_up">clean_up()</h4>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span><span class="p">(</span><span class="n">scope</span><span class="o">=</span><span class="s2">&#34;session&#34;</span><span class="p">,</span> <span class="n">autouse</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">clean_up</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="k">yield</span>
</span></span><span class="line"><span class="cl">    <span class="n">default_pets</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;1&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;ginger&#34;</span><span class="p">,</span> <span class="s2">&#34;breed&#34;</span><span class="p">:</span> <span class="s2">&#34;bengal&#34;</span><span class="p">,</span> <span class="s2">&#34;price&#34;</span><span class="p">:</span> <span class="mi">100</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;2&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;sam&#34;</span><span class="p">,</span> <span class="s2">&#34;breed&#34;</span><span class="p">:</span> <span class="s2">&#34;husky&#34;</span><span class="p">,</span> <span class="s2">&#34;price&#34;</span><span class="p">:</span> <span class="mi">10</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;3&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;guido&#34;</span><span class="p">,</span> <span class="s2">&#34;breed&#34;</span><span class="p">:</span> <span class="s2">&#34;python&#34;</span><span class="p">,</span> <span class="s2">&#34;price&#34;</span><span class="p">:</span> <span class="mi">518</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">abs_file_path</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">abspath</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="vm">__file__</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="n">json_path</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">abs_file_path</span><span class="p">,</span> <span class="s2">&#34;../&#34;</span><span class="p">,</span> <span class="s2">&#34;test_api&#34;</span><span class="p">,</span> <span class="s2">&#34;core&#34;</span><span class="p">,</span> <span class="s2">&#34;pets.json&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">json_path</span><span class="p">,</span> <span class="s2">&#34;w&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">pet_store</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">json</span><span class="o">.</span><span class="n">dump</span><span class="p">(</span><span class="n">default_pets</span><span class="p">,</span> <span class="n">pet_store</span><span class="p">,</span> <span class="n">indent</span><span class="o">=</span><span class="mi">4</span><span class="p">)</span>
</span></span></code></pre></div><p>The second fixture we define is called <code>clean_up</code>, because of the <code>yield</code> line, this function will run after all of
our tests have completed. The <code>yield</code> command is related to generators, you can read
<a href="https://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do">more here</a>. In our case, it&rsquo;s used in Pytest
fixtures so that we can run some cleanup jobs after our test is completed. In this example, I am simply replacing the
contents of the JSON file which acts as a data store (like a database), to its default values before the test was run.</p>
<blockquote>
<p>Since pytest-3.0, fixtures using the normal fixture decorator can use a yield statement to provide fixture values and execute teardown code - Pytest Docs</p>
</blockquote>
<h3 id="test_pets_controllerpy">test_pets_controller.py</h3>
<p>Now we have gone over the setup required for our tests, let&rsquo;s take a look at how we can test our
code. So our first test looks like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">test_get_all_pets</span><span class="p">(</span><span class="n">client</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;/api/v1/pet&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">expected_json</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span><span class="s2">&#34;id&#34;</span><span class="p">:</span> <span class="s2">&#34;1&#34;</span><span class="p">,</span> <span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;ginger&#34;</span><span class="p">,</span> <span class="s2">&#34;breed&#34;</span><span class="p">:</span> <span class="s2">&#34;bengal&#34;</span><span class="p">,</span> <span class="s2">&#34;price&#34;</span><span class="p">:</span> <span class="mi">100</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span><span class="s2">&#34;id&#34;</span><span class="p">:</span> <span class="s2">&#34;2&#34;</span><span class="p">,</span> <span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;sam&#34;</span><span class="p">,</span> <span class="s2">&#34;breed&#34;</span><span class="p">:</span> <span class="s2">&#34;husky&#34;</span><span class="p">,</span> <span class="s2">&#34;price&#34;</span><span class="p">:</span> <span class="mi">10</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span><span class="s2">&#34;id&#34;</span><span class="p">:</span> <span class="s2">&#34;3&#34;</span><span class="p">,</span> <span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;guido&#34;</span><span class="p">,</span> <span class="s2">&#34;breed&#34;</span><span class="p">:</span> <span class="s2">&#34;python&#34;</span><span class="p">,</span> <span class="s2">&#34;price&#34;</span><span class="p">:</span> <span class="mi">518</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span> <span class="o">==</span> <span class="n">expected_json</span>
</span></span></code></pre></div><p>It&rsquo;s a very simple test, here we use the <code>app</code> fixture we defined above. This <code>client</code> fixture can be used
because we are using the <code>pytest-flask</code> library. As you can see it looks very similar to <code>requests</code>, where
we give it a path <code>/API/v1/pet</code> and then tell it what kind of request to make <code>client.get</code>.
Whilst the syntax between the <code>requests</code> library and the <code>client</code> fixture is almost identical. One big
difference that always seems to trip me up is, in <code>requests</code> to get the JSON data from the <code>response</code> object would be
<code>response.json()</code> i.e it is a function. However in <code>client</code> (<code>pytest-flask</code>) fixture do get the JSON data we do
<code>response.json</code> which is just an attribute of the object not a function.</p>
<p>The test itself is very simple, it&rsquo;s making a request to get all pets in the pet store. We then compare that with=
what we expect to be in the pet store <code>assert response.json == expected_json</code>.</p>
<p>The next test we have looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nd">@pytest.mark.parametrize</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;pet_data, expected_status, expected_data&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">({</span><span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;Yolo&#34;</span><span class="p">,</span> <span class="s2">&#34;breed&#34;</span><span class="p">:</span> <span class="s2">&#34;shorthair&#34;</span><span class="p">,</span> <span class="s2">&#34;price&#34;</span><span class="p">:</span> <span class="mi">100</span><span class="p">},</span> <span class="mi">201</span><span class="p">,</span> <span class="p">{</span><span class="s2">&#34;id&#34;</span><span class="p">:</span> <span class="mi">4</span><span class="p">}),</span>
</span></span><span class="line"><span class="cl">        <span class="p">({},</span> <span class="mi">400</span><span class="p">,</span> <span class="p">{}),</span>
</span></span><span class="line"><span class="cl">        <span class="p">({</span><span class="s2">&#34;a&#34;</span><span class="p">:</span> <span class="s2">&#34;b&#34;</span><span class="p">},</span> <span class="mi">400</span><span class="p">,</span> <span class="p">{}),</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">test_add_a_pet</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">pet_data</span><span class="p">,</span> <span class="n">expected_status</span><span class="p">,</span> <span class="n">expected_data</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;/api/v1/pet&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">json</span><span class="o">=</span><span class="n">pet_data</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="n">expected_status</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">response</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span> <span class="o">==</span> <span class="n">expected_data</span>
</span></span></code></pre></div><p>This test is attempting to add a new pet to the store. It&rsquo;s similar to the other test we still use
the <code>client</code> fixture to make the request. This time we also give it some <code>json</code> data hence we provide the <code>json</code>
argument <code>json=pet_data</code> this automatically sets the headers correctly so the server knows it&rsquo;s receiving
JSON data.</p>
<p>We also use a decorate called <code>@pytest.mark.parametrize</code>. This allows us to run our tests against a list
of data. So we don&rsquo;t have to write the same test x number of times. We just pass the test different
arguments. Pytest will run this test x number of times once for each item in the list.
So, for example, the first time the test runs:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">pet_data</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;Yolo&#34;</span><span class="p">,</span> <span class="s2">&#34;breed&#34;</span><span class="p">:</span> <span class="s2">&#34;shorthair&#34;</span><span class="p">,</span> <span class="s2">&#34;price&#34;</span><span class="p">:</span> <span class="mi">100</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="n">expected_status</span> <span class="o">=</span> <span class="mi">200</span>
</span></span><span class="line"><span class="cl"><span class="n">expected_data</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;id&#34;</span><span class="p">:</span> <span class="mi">4</span><span class="p">}</span>
</span></span></code></pre></div><p>The second like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">pet_data</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl"><span class="n">expected_status</span> <span class="o">=</span> <span class="mi">200</span>
</span></span><span class="line"><span class="cl"><span class="n">expected_data</span> <span class="o">=</span> <span class="p">{}</span>
</span></span></code></pre></div><p>And so on and so on. This helps keep our test file smaller and keeps the DRY (do not repeat yourself).
A very nice feature of Pytest and one I use heavily.</p>
<p>The final test we have in this file looks like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">test_add_pet_fail_json</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">mocker</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">pet_data</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;Yolo&#34;</span><span class="p">,</span> <span class="s2">&#34;breed&#34;</span><span class="p">:</span> <span class="s2">&#34;shorthair&#34;</span><span class="p">,</span> <span class="s2">&#34;price&#34;</span><span class="p">:</span> <span class="mi">100</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;/api/v1/pet&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">mock</span> <span class="o">=</span> <span class="n">mocker</span><span class="o">.</span><span class="n">patch</span><span class="p">(</span><span class="s2">&#34;connexion.request&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">mock</span><span class="o">.</span><span class="n">is_json</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">json</span><span class="o">=</span><span class="n">pet_data</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">400</span>
</span></span></code></pre></div><p>At last, we see <code>pytest-mock</code> being used via the <code>mocker</code> fixture we automatically get access to.
The <code>mocker</code> is just a simple wrapper around the <code>unittest.mock</code> module. The main difference being
the mock on exists for the duration of that test. Mocking is often used when unit testing and we cannot
rely on external dependencies such as database connections or another web service.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">add_pet</span><span class="p">(</span><span class="n">body</span><span class="p">):</span>  <span class="c1"># noqa: E501</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># ...</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">connexion</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">is_json</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">body</span> <span class="o">=</span> <span class="n">Pet</span><span class="o">.</span><span class="n">from_dict</span><span class="p">(</span><span class="n">connexion</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">get_json</span><span class="p">())</span>  <span class="c1"># noqa: E501</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># ...</span>
</span></span></code></pre></div><p>In this example, we want to mock the part of connexion that checks if the data being sent is valid JSON.
We want the <code>connexion.request.is_json</code> to return <code>False</code>, we can do this like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">mock</span> <span class="o">=</span> <span class="n">mocker</span><span class="o">.</span><span class="n">patch</span><span class="p">(</span><span class="s2">&#34;connexion.request&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">mock</span><span class="o">.</span><span class="n">is_json</span> <span class="o">=</span> <span class="kc">False</span>
</span></span></code></pre></div><p>Since <code>is_json</code> is an attribute of the <code>connexion.request</code> module and not a function we need to set
it false on another line. If <code>is_json</code> was a function that we wanted to return <code>False</code> we could&rsquo;ve done
<code>mocker.patch(&quot;connexion.request.is_json&quot;)</code> instead.</p>
<p>You can run the tests locally by running the <code>pytest</code> command or if you want to run the code in this article, you can
by doing the following:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">gcl https://gitlab.com/hmajid2301/articles.git
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> 27.<span class="se">\ </span>Mocking<span class="se">\ </span>in<span class="se">\ </span>Flask<span class="se">\ </span>with<span class="se">\ </span>Pytest/source_code
</span></span><span class="line"><span class="cl">virtualenv .venv
</span></span><span class="line"><span class="cl"><span class="nb">source</span> .venv/bin/activate
</span></span><span class="line"><span class="cl">pip install -r requirements.txt
</span></span><span class="line"><span class="cl">pytest
</span></span></code></pre></div><p>That&rsquo;s it, the examples above cover most of the things you&rsquo;ll need to mock and test your connexion
web service.</p>
<blockquote>
<p>INFO: <code>pytest-flask</code> provides a whole bunch of other features that may be useful, you can find the full list <a href="https://pytest-flask.readthedocs.io/en/latest/features.html">here</a></p>
</blockquote>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2020-06-09-testing-a-connexion-flask-application-with-pytest/source_code">Example source code</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>An example React Native Project Structure</title>
      <link>https://haseebmajid.dev/posts/2020-05-31-an-example-react-native-project-structure/</link>
      <pubDate>Sun, 31 May 2020 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2020-05-31-an-example-react-native-project-structure/</guid>
      <description>&lt;p&gt;In this article, I will go over an example project structure you can use for your React Native projects.
This of couse my opinion so feel free to tweak the structure to your needs/preferences.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://gitlab.com/hmajid2301/stegappasaurus/&#34;&gt;Link to project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;stegappasaurus.haseebmajid.dev/&#34;&gt;Link to Docz Website&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;project-structure&#34;&gt;Project Structure&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── android
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── app.json
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── App.tsx
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── babel.config.js
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── .buckconfig
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── CHANGELOG.md
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── CODE_OF_CONDUCT.md
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── CONTRIBUTING.md
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── docs
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── doczrc.js
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── .eslintrc.js
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── gatsby-node.js
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── .gitignore
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── .gitlab
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── .gitlab-ci.yml
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── .history
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── images
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── index.d.ts
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── index.js
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── ios
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── jest.config.js
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── LICENSE
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── metro.config.js
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── __mocks__
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── node_modules
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── package.json
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── prettier.config.js
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── public
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── react-native.config.js
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── README.md
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── src
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── __tests__
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── tsconfig.json
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── util
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── .watchmanconfig
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;└── yarn.lock
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;configs&#34;&gt;Configs&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s briefly go over the various config files used in this project.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this article, I will go over an example project structure you can use for your React Native projects.
This of couse my opinion so feel free to tweak the structure to your needs/preferences.</p>
<ul>
<li><a href="https://gitlab.com/hmajid2301/stegappasaurus/">Link to project</a></li>
<li><a href="stegappasaurus.haseebmajid.dev/">Link to Docz Website</a></li>
</ul>
<h2 id="project-structure">Project Structure</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">.
</span></span><span class="line"><span class="cl">├── android
</span></span><span class="line"><span class="cl">├── app.json
</span></span><span class="line"><span class="cl">├── App.tsx
</span></span><span class="line"><span class="cl">├── babel.config.js
</span></span><span class="line"><span class="cl">├── .buckconfig
</span></span><span class="line"><span class="cl">├── CHANGELOG.md
</span></span><span class="line"><span class="cl">├── CODE_OF_CONDUCT.md
</span></span><span class="line"><span class="cl">├── CONTRIBUTING.md
</span></span><span class="line"><span class="cl">├── docs
</span></span><span class="line"><span class="cl">├── doczrc.js
</span></span><span class="line"><span class="cl">├── .eslintrc.js
</span></span><span class="line"><span class="cl">├── gatsby-node.js
</span></span><span class="line"><span class="cl">├── .gitignore
</span></span><span class="line"><span class="cl">├── .gitlab
</span></span><span class="line"><span class="cl">├── .gitlab-ci.yml
</span></span><span class="line"><span class="cl">├── .history
</span></span><span class="line"><span class="cl">├── images
</span></span><span class="line"><span class="cl">├── index.d.ts
</span></span><span class="line"><span class="cl">├── index.js
</span></span><span class="line"><span class="cl">├── ios
</span></span><span class="line"><span class="cl">├── jest.config.js
</span></span><span class="line"><span class="cl">├── LICENSE
</span></span><span class="line"><span class="cl">├── metro.config.js
</span></span><span class="line"><span class="cl">├── __mocks__
</span></span><span class="line"><span class="cl">├── node_modules
</span></span><span class="line"><span class="cl">├── package.json
</span></span><span class="line"><span class="cl">├── prettier.config.js
</span></span><span class="line"><span class="cl">├── public
</span></span><span class="line"><span class="cl">├── react-native.config.js
</span></span><span class="line"><span class="cl">├── README.md
</span></span><span class="line"><span class="cl">├── src
</span></span><span class="line"><span class="cl">├── __tests__
</span></span><span class="line"><span class="cl">├── tsconfig.json
</span></span><span class="line"><span class="cl">├── util
</span></span><span class="line"><span class="cl">├── .watchmanconfig
</span></span><span class="line"><span class="cl">└── yarn.lock
</span></span></code></pre></div><h2 id="configs">Configs</h2>
<p>Let&rsquo;s briefly go over the various config files used in this project.</p>
<blockquote>
<p><strong>Note:</strong> Not all of this will be relevant for your project. You can use the ones relevant to your project.</p>
</blockquote>
<ul>
<li><code>app.json</code>: Used by React Native contains the name of your app.</li>
<li><code>.buckconfig</code>: Used to speed up builds plus more.</li>
<li><code>babel.config.js</code>: The config used by Babel, which transpile our code into compliant ES5, so we can use all the newest and greatest features from JavaScript. I think one of the best babel plugins you can use is the babel-module-resolver so we have cleaner imports more info <a href="https://dev.to/hmajid2301/better-imports-with-typescript-aliases-babel-and-tspath-40ne">here</a>.</li>
<li><code>doczrc.js</code>: The config is used by Docz, which is used to create a website from Markdown files, the config is used to set the theme and the order of the sidebar.</li>
<li><code>.eslintrc.js</code>: I use eslint as my linter of choice. This is the config used to set up all the various options. Including relevant config to use with Typescript and Prettier.</li>
<li><code>gatsby-node.js</code>: Docz uses Gatsby &ldquo;behind the scenes&rdquo;, you only need this file if you intend to use Docz.</li>
<li><code>jest.config.js</code>: Since this is a React Native project I also use Jest. A test runner created by Facebook. This file is used to set up various bits of config such as allowing me to use the same module import resolution and using it with Typescript (babel-jest).</li>
<li><code>metro.config.js</code>: Metro is a React Native javascript bundler.</li>
<li><code>package.json</code>: The file use to manage dependencies and build scripts.</li>
<li><code>prettier.config.js</code>: The config for the Prettier code formatter.</li>
<li><code>react-native.config.js</code>: As of React Native 0.60 you use this file to allow you to import custom fonts and assets into your React Native project.</li>
<li><code>tsconfig.json</code>: Since I am using Typescript this is the required config for Typescript.</li>
<li><code>.watchmanconfig</code>: Is a file watcher used for hot reloading.</li>
<li><code>yarn.lock</code>: Not quite config but used by package.json.</li>
</ul>
<p>The following config files, <code>app.json</code>, <code>.buckconfig</code>, <code>metro.config.js</code>, <code>.watchmanconfig</code>, were unchanged after creating the project. Using the following command:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">npx react-native init AwesomeTSProject --template react-native-template-typescript
</span></span></code></pre></div><h2 id="testing">Testing</h2>
<p>For testing, I have the following two folders:</p>
<h3 id="mocks">Mocks</h3>
<p>The <code>__mocks__</code> folder. Used to mock out various third party modules and functions. Here is an example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">.
</span></span><span class="line"><span class="cl">├── bugsnag-react-native.js
</span></span><span class="line"><span class="cl">├── @react-native-community
</span></span><span class="line"><span class="cl">│   └── cameraroll.js
</span></span><span class="line"><span class="cl">├── react-native-image-picker.js
</span></span><span class="line"><span class="cl">├── react-native-navigation-bar-color.js
</span></span><span class="line"><span class="cl">├── react-native-permissions.js
</span></span><span class="line"><span class="cl">├── react-native-share-extension.js
</span></span><span class="line"><span class="cl">├── react-native-share.js
</span></span><span class="line"><span class="cl">├── react-native-snackbar.js
</span></span><span class="line"><span class="cl">└── rn-fetch-blob.js
</span></span></code></pre></div><p>Where <code>bugsnag-react-native.js</code> looks something like the following:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">Configuration</span><span class="o">:</span> <span class="nx">jest</span><span class="p">.</span><span class="nx">fn</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">  <span class="nx">Client</span><span class="o">:</span> <span class="nx">jest</span><span class="p">.</span><span class="nx">fn</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">({</span> <span class="nx">notify</span><span class="o">:</span> <span class="nx">jest</span><span class="p">.</span><span class="nx">fn</span><span class="p">()</span> <span class="p">})),</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><h3 id="tests">Tests</h3>
<p>The <code>__tests__</code> folder contains all of my tests. The structure matches the structure of the <code>src</code> folder.
So it&rsquo;s easier to find tests. Some people prefer to keep their tests in the same folder as their components. They will also
keep their storybook config in the component folder, so everything related to that component exists in that folder. However
I prefer to keep my tests separate to my source code.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">.
</span></span><span class="line"><span class="cl">├── <span class="nb">set</span> upTests.ts
</span></span><span class="line"><span class="cl">└── src
</span></span><span class="line"><span class="cl">    ├── actions
</span></span><span class="line"><span class="cl">    │   ├── Snackbar.test.ts
</span></span><span class="line"><span class="cl">    │   └── Steganography
</span></span><span class="line"><span class="cl">    ├── components
</span></span><span class="line"><span class="cl">    │   ├── AboutList.test.tsx
</span></span><span class="line"><span class="cl">    │   ├── AppHeader.test.tsx
</span></span><span class="line"><span class="cl">    │   ├── ImageMessage.test.tsx
</span></span><span class="line"><span class="cl">    │   ├── ImageProgress.test.tsx
</span></span><span class="line"><span class="cl">    │   ├── MainHeader.test.tsx
</span></span><span class="line"><span class="cl">    │   ├── MarkdownModal.test.tsx
</span></span><span class="line"><span class="cl">    │   └── Modal.test.tsx
</span></span><span class="line"><span class="cl">    └── views
</span></span><span class="line"><span class="cl">        ├── Home
</span></span><span class="line"><span class="cl">        └── Settings
</span></span></code></pre></div><h2 id="documentation">Documentation</h2>
<p>The following files/folders are used to document the project.</p>
<ul>
<li><code>docs</code>: Contains the markdown files used by the Docz website.</li>
<li><code>public</code>: Used to contain some static files used by Docz such as favicons.</li>
<li><code>README.md</code>: The first page the user will see when visiting the repo.</li>
<li><code>CHANGELOG.md</code>: The changes to the project in the <a href="https://keepachangelog.com/en/1.0.0/">Keepachangelog</a> format.</li>
<li><code>CODE_OF_CONDUCT.md</code>: How to &ldquo;behave within&rdquo; the project.</li>
<li><code>CONTRIBUTING.md</code>: How to contribute to the project, helping users getting started with this project.</li>
<li><code>images</code>: Used to store the original SVG images converted to PNGs.</li>
</ul>
<h2 id="gitlab--git">Gitlab / Git</h2>
<p>This project is available on Gitlab, so here are the specific files related to git/Gitlab:</p>
<ul>
<li><code>.gitlab</code>: Contains templates for merge requests and issues.</li>
<li><code>.gitlab-ci.yml</code>: Is the CI file, which defines what jobs are run on Gitlab CI.</li>
<li><code>.gitignore</code>: Used by git to determine what files to ignore, when committing changes. Generated from <a href="https://www.gitignore.io/">gitignore.io</a></li>
</ul>
<h3 id="gitlab">.gitlab</h3>
<p>Taking a closer look at the <code>.gitlab</code> folder you can see the different templates I have:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">.
</span></span><span class="line"><span class="cl">├── issue_templates
</span></span><span class="line"><span class="cl">│   ├── bug.md
</span></span><span class="line"><span class="cl">│   ├── feature.md
</span></span><span class="line"><span class="cl">│   └── question.md
</span></span><span class="line"><span class="cl">└── merge_request_templates
</span></span><span class="line"><span class="cl">    ├── merge_request.md
</span></span><span class="line"><span class="cl">    └── release.md
</span></span></code></pre></div><p>If someone creates a new issue using the <code>bug</code> template, they will get the following template to edit when
raising their issue. Making it easier for others to give the relevant information required to resolve the
issue.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">---
</span></span><span class="line"><span class="cl">name: &#34;🐛 Bug&#34;
</span></span><span class="line"><span class="cl">---
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gh"># Bug Report
</span></span></span><span class="line"><span class="cl"><span class="gh"></span>
</span></span><span class="line"><span class="cl"><span class="gu">## Current Behaviour
</span></span></span><span class="line"><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="cl"><span class="c">&lt;!-- What is the current behaviour --&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gh"># ...
</span></span></span></code></pre></div><h2 id="source-code">Source Code</h2>
<p>Now onto the more interesting part of this project.</p>
<ul>
<li><code>android</code>: All the specific native code for Android. You will only need to edit this if you need to write Android specific code in Java/Kotlin or edit the way your application is built.</li>
<li><code>ios</code>: Same as above except for IOS.</li>
</ul>
<h3 id="src">src</h3>
<p>Now most of the code related to this project exists within the <code>src/</code> folder.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">.
</span></span><span class="line"><span class="cl">├── actions
</span></span><span class="line"><span class="cl">│   ├── Bugsnag
</span></span><span class="line"><span class="cl">│   ├── Share
</span></span><span class="line"><span class="cl">│   ├── Snackbar
</span></span><span class="line"><span class="cl">│   └── Steganography
</span></span><span class="line"><span class="cl">├── assets
</span></span><span class="line"><span class="cl">│   ├── fonts
</span></span><span class="line"><span class="cl">│   └── images
</span></span><span class="line"><span class="cl">├── components
</span></span><span class="line"><span class="cl">├── AboutList
</span></span><span class="line"><span class="cl">│   ├── AboutList.tsx
</span></span><span class="line"><span class="cl">│   └── index.ts
</span></span><span class="line"><span class="cl">│   ├── ImageMessage
</span></span><span class="line"><span class="cl">│   ├── ImageProgress
</span></span><span class="line"><span class="cl">│   ├── IntroSlider
</span></span><span class="line"><span class="cl">│   ├── Loader
</span></span><span class="line"><span class="cl">│   ├── Logo
</span></span><span class="line"><span class="cl">│   ├── MarkdownModal
</span></span><span class="line"><span class="cl">│   ├── Modal
</span></span><span class="line"><span class="cl">│   └── PhotoAlbumList
</span></span><span class="line"><span class="cl">├── constants
</span></span><span class="line"><span class="cl">│   ├── colors.ts
</span></span><span class="line"><span class="cl">│   ├── fonts.ts
</span></span><span class="line"><span class="cl">│   ├── themes.ts
</span></span><span class="line"><span class="cl">│   └── types.ts
</span></span><span class="line"><span class="cl">├── data
</span></span><span class="line"><span class="cl">├── providers
</span></span><span class="line"><span class="cl">└── views
</span></span><span class="line"><span class="cl">    ├── Home
</span></span><span class="line"><span class="cl">    ├── MainApp.tsx
</span></span><span class="line"><span class="cl">    ├── Setting
</span></span><span class="line"><span class="cl">    └── Settings.tsx
</span></span></code></pre></div><ul>
<li><code>actions</code>: Contains actions such as a snack bar which can be shown.</li>
<li><code>assets</code>: Static assets such as images and fonts.</li>
<li><code>components</code>: Components typically will be used by multiple views. Each component has its own folder.</li>
<li><code>constants</code>: Used to store colours, common types and fonts.</li>
<li><code>data</code>: (JSON) data used by the components.</li>
<li><code>providers</code>: React contexts, which will be consumed by other components to store state.</li>
<li><code>views</code>: The different pages the users will see. Since settings and home have sub-pages those, exist within those folders.</li>
</ul>
<p>That&rsquo;s it, that my &ldquo;basic&rdquo; structure I&rsquo;ve used for a React Native project.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/stegappasaurus/">Example React Native Project</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to make PrismJS code blocks editable</title>
      <link>https://haseebmajid.dev/posts/2020-05-10-how-to-make-prismjs-code-blocks-editable/</link>
      <pubDate>Sun, 10 May 2020 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2020-05-10-how-to-make-prismjs-code-blocks-editable/</guid>
      <description>&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2020-05-10-how-to-make-prismjs-code-blocks-editable/images/main.gif&#34;
        type=&#34;&#34;
        alt=&#34;demo&#34;
        
      /&gt;&lt;/p&gt;
&lt;p&gt;In this article, we will go over how you can make PrismJS (syntax highlighted) code blocks editable.&lt;/p&gt;
&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://prismjs.com/&#34;&gt;PrismJS&lt;/a&gt; can be used to add syntax highlighting to code blocks on our website. For a persona
project of mine, &lt;a href=&#34;composerisation.haseebmajid.dev/&#34;&gt;composersiation&lt;/a&gt; #ShamelessPlug :plug:, I needed to allow the user
to paste in their own (docker-compose) yaml files. So let&amp;rsquo;s take a look how we can let a user to first edit a code block
and then re-run PrismJS to add syntax highlighting.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><img
        loading="lazy"
        src="/posts/2020-05-10-how-to-make-prismjs-code-blocks-editable/images/main.gif"
        type=""
        alt="demo"
        
      /></p>
<p>In this article, we will go over how you can make PrismJS (syntax highlighted) code blocks editable.</p>
<h2 id="introduction">Introduction</h2>
<p><a href="https://prismjs.com/">PrismJS</a> can be used to add syntax highlighting to code blocks on our website. For a persona
project of mine, <a href="composerisation.haseebmajid.dev/">composersiation</a> #ShamelessPlug :plug:, I needed to allow the user
to paste in their own (docker-compose) yaml files. So let&rsquo;s take a look how we can let a user to first edit a code block
and then re-run PrismJS to add syntax highlighting.</p>
<p>So our HTML will look something like this.</p>
<blockquote>
<p>Note: When I refer to &ldquo;code block&rdquo; I am referring to entire thing including the <code>pre</code> and the <code>code</code> tags.</p>
</blockquote>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">head</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">link</span>
</span></span><span class="line"><span class="cl">    <span class="na">rel</span><span class="o">=</span><span class="s">&#34;stylesheet&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="na">type</span><span class="o">=</span><span class="s">&#34;text/css&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="na">href</span><span class="o">=</span><span class="s">&#34;stylesheets/prism.css&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="na">rel</span><span class="o">=</span><span class="s">&#34;stylesheet&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">head</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">pre</span>
</span></span><span class="line"><span class="cl">  <span class="na">onPaste</span><span class="o">=</span><span class="s">&#34;setTimeout(function() {onPaste();}, 0)&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="na">id</span><span class="o">=</span><span class="s">&#34;editable&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="na">contenteditable</span>
</span></span><span class="line"><span class="cl"><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">code</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;yaml&#34;</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;language-yaml&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">code</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">pre</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;javascript/prism.js&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>In this file we import the <code>prism.css</code> stylesheet, there are many themes you can choose
from in this example we will use the default theme. We will also import <code>prism.js</code>, these are the two files required to use PrismJS.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">pre</span>
</span></span><span class="line"><span class="cl">  <span class="na">onPaste</span><span class="o">=</span><span class="s">&#34;setTimeout(function() {onPaste();}, 0)&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="na">id</span><span class="o">=</span><span class="s">&#34;editable&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="na">contenteditable</span>
</span></span><span class="line"><span class="cl"><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">code</span> <span class="na">id</span><span class="o">=</span><span class="s">&#34;yaml&#34;</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;language-yaml&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">code</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">pre</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>Next we create the code block on web page. Not the class on the <code>code</code> tag is <code>language-yaml</code>. To use PrismJS we
need to give the <code>code</code> a tag a class of <code>language-x</code> where x is the language we want syntax highlighting for.
You can find a full list of <a href="https://prismjs.com/#supported-languages">supported languages here</a>.</p>
<p>To allow users to paste and edit the code block we add <code>contenteditable</code> to the <code>pre</code> tag. The reason we add it to the <code>pre</code>
tag and not the <code>code</code> tag is, when PrismJS has run it will edit the <code>code</code> block to include <code>span</code>&rsquo;s and other html elements,
to do the syntax highlighting it makes it a lot harder for the user to copy and paste when you edit the <code>code</code> tag as a pose to
<code>pre</code> tag. The <code>pre</code> tag also has <code>onPaste=&quot;setTimeout(function() {onPaste();}, 0)&quot;</code> this means that after the user has pasted
into the <code>pre</code> tag this function will be called. In this case we call a function called <code>onPaste()</code>. However we use a <code>setTimeout</code>,
so that the browser has enough time to update the <code>pre</code> tag, else the <code>pre</code>/<code>code</code> tags will still contain the previous text before
the paste.</p>
<h2 id="javascript">JavaScript</h2>
<p>Now the user can paste directly into the code block. How do we force a re-render ? Let&rsquo;s take a look at <code>onPaste</code> function which
is called everytime the user paste&rsquo;s into our code block.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">onPaste</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">editable</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s2">&#34;editable&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">dockerCompose</span> <span class="o">=</span> <span class="nx">editable</span><span class="p">.</span><span class="nx">innerText</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">editable</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="s1">&#39;&lt;code id=&#34;yaml&#34; class=&#34;language-yaml&#34;&gt;&lt;/code&gt;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">yaml</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s2">&#34;yaml&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nx">yaml</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="nx">Prism</span><span class="p">.</span><span class="nx">highlight</span><span class="p">(</span><span class="nx">dockerCompose</span><span class="p">,</span> <span class="nx">Prism</span><span class="p">.</span><span class="nx">languages</span><span class="p">.</span><span class="nx">yaml</span><span class="p">,</span> <span class="s2">&#34;yaml&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>So first we get the <code>editable</code> element (our <code>pre</code> tag). Next we get the innerText of said element. This should be the new content
the user wants to paste into the <code>pre</code> tag. Sometimes when you copy/paste into the code block the old <code>code</code> tag get&rsquo;s deleted
so just in case we add the <code>code</code> tag back in. As this is where PrismJS will render our &ldquo;new&rdquo; yaml &ldquo;code&rdquo; in. This is done like so
<code>editable.innerHTML = '&lt;code id=&quot;yaml&quot; class=&quot;language-yaml&quot;&gt;&lt;/code&gt;';</code>, this code replaces all the &ldquo;children&rdquo; of the <code>pre</code> tag
with this new code block. Next we get the <code>code</code> tag with id <code>yaml</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">yaml</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="nx">Prism</span><span class="p">.</span><span class="nx">highlight</span><span class="p">(</span><span class="nx">dockerCompose</span><span class="p">,</span> <span class="nx">Prism</span><span class="p">.</span><span class="nx">languages</span><span class="p">.</span><span class="nx">yaml</span><span class="p">,</span> <span class="s2">&#34;yaml&#34;</span><span class="p">);</span>
</span></span></code></pre></div><p>Finally the main part of our code which actually highlights our code. We pass the newly pasted yaml it&rsquo;s stored in <code>dockerCompose</code>
variable. Next we tell Prism what langauge to use <code>Prism.languages.yaml</code> (this is the language grammar0 and finally we pass the
language name in this case yaml. Then we set this as the <code>innerHTML</code> of the <code>code</code> tag.</p>
<p>That&rsquo;s it! Now when the user paste&rsquo;s in new yaml code, it&rsquo;ll be automatically syntax highlighted by PrismJS. This process
can of course, also be used for AJAX content as well. If you make an API request and the API responds with code that needs
to be syntax highlighted.</p>
<blockquote>
<p>Note: The code in this project isn&rsquo;t particularly clean, it&rsquo;s mostly all in one file. This is just to make the example a bit easier to follow in reality you would likely split this into multiple files.</p>
</blockquote>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2020-05-10-how-to-make-prismjs-code-blocks-editable/source_code">Example source code</a></li>
<li><a href="https://composerisation.haseebmajid.dev/#yaml">Example Project</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to use DinD with Gitlab CI</title>
      <link>https://haseebmajid.dev/posts/2020-05-01-how-to-use-dind-with-gitlab-ci/</link>
      <pubDate>Fri, 01 May 2020 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2020-05-01-how-to-use-dind-with-gitlab-ci/</guid>
      <description>&lt;p&gt;Like most developers, we want to be able to automate as many and as much of processes as possible. Pushing Docker
images to a registry is a task that can easily be automated. In this article, we will cover how you can use
Gitlab CI to build and publish your Docker images, to the Gitlab registry. However, you can also very easily
edit this to push your images to DockerHub as well.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Like most developers, we want to be able to automate as many and as much of processes as possible. Pushing Docker
images to a registry is a task that can easily be automated. In this article, we will cover how you can use
Gitlab CI to build and publish your Docker images, to the Gitlab registry. However, you can also very easily
edit this to push your images to DockerHub as well.</p>
<p>A quick aside on terminology related to Docker:</p>
<ul>
<li>container: An instance of an image is called a container (<code>docker run</code>)</li>
<li>image: A set of immutable layers (<code>docker build</code>)</li>
<li>hub: The official registry where you can get more Docker images from (<code>docker pull</code>)</li>
</ul>
<h2 id="example">Example</h2>
<p>Here is an example <code>.gitlab-ci.yml</code> file which can be used to build and push your Docker images to the Gitlab registry.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">DOCKER_DRIVER</span><span class="p">:</span><span class="w"> </span><span class="l">overlay2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">docker:dind</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">publish-docker</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">docker</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">export VERSION_TAG=v1.2.3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">docker login ${CI_REGISTRY} -u gitlab-ci-token -p ${CI_BUILD_TOKEN}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">docker build -t ${CI_REGISTRY_IMAGE}:latest -t ${CI_REGISTRY_IMAGE}:${VERSION_TAG}  .</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">docker push ${CI_REGISTRY_IMAGE}:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">docker push ${CI_REGISTRY_IMAGE}:${VERSION_TAG}</span><span class="w">
</span></span></span></code></pre></div><h2 id="explained">Explained</h2>
<p>The code above may be a bit confusing, it might be a lot to take in. So now we will break it down line by line.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">DOCKER_DRIVER</span><span class="p">:</span><span class="w"> </span><span class="l">overlay2</span><span class="w">
</span></span></span></code></pre></div><p>In our first couple of lines, we define some variables which will be used by all our jobs (the variables are global).
We define a variable <code>DOCKER_DRIVER: overlay2</code>, this helps speed our Docker containers a bit because by default it
uses <code>vfs</code> which is slower
<a href="https://docs.gitlab.com/ce/ci/docker/using_docker_build.html#using-the-overlayfs-driver">learn more here</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">random-job</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">DOCKER_DRIVER</span><span class="p">:</span><span class="w"> </span><span class="l">overlay2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">echo &#34;HELLO&#34;</span><span class="w">
</span></span></span></code></pre></div><blockquote>
<p>Note we could just as easily define <code>variables</code> just within our job as well like you see in the example above.</p>
</blockquote>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">docker:dind</span><span class="w">
</span></span></span></code></pre></div><p>The next couple of lines define a service. A service is a Docker image which links during our job(s). Again in this
example, it is defined globally and will link to all of our jobs. We could very easily define it within our job just
like in the <code>variables</code> example. The <a href="https://github.com/docker-library/docker/blob/157869f94ea90e2acb4d0f77045d99079ead821c/18.02/dind/dockerd-entrypoint.sh"><code>docker:dind</code></a>
image automatically using its <code>entrypoint</code> starts a docker daemon. We need to use this daemon to build/push our
Docker images within CI.</p>
<p>The <code>docker:dind</code> (dind = Docker in Docker) image is almost identical to the <code>docker</code> image. The difference being the dind image
starts a Docker daemon. In this example, the job will use the <code>docker</code> image as the client and connect to the daemon
running in this container.</p>
<p>We could also just use the <code>dind</code> image in our job and simply start <code>dockerd</code> (&amp; = in the background) in the first line.
The <code>dockerd</code> command starts the Docker daemon as a client, so we can then communicate with the other Docker daemon.
It would achieve the same outcome. I think the service approach is a bit cleaner but as already stated either approach
would work.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">publish-docker</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">docker:dind</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">dockerd &amp;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="l">...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">docker push ${CI_REGISTRY_IMAGE}:${VERSION_TAG}</span><span class="w">
</span></span></span></code></pre></div><blockquote>
<p>Info: One common use case of Gitlab CI services is to spin up databases like MySQL. We can then connect to it within our job, run our tests. It can simplify our jobs by quite a bit.</p>
</blockquote>
<blockquote>
<p>Note: There are several other ways we could also build/push our images. This is the <a href="https://gitlab.com/gitlab-examples/docker/blob/master/.gitlab-ci.yml">recommended approach</a>.</p>
</blockquote>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">publish</span><span class="w">
</span></span></span></code></pre></div><p>Next, we define our stages and give them names. Each job must have a valid stage attached to it. Stages are used to
determine when a job will be run in our CI pipeline. If two jobs have the same stage, then they will run in parallel.
The stages defined earlier will run first so order does matter. However in this example, we only have one stage and
one job so this isn&rsquo;t super important, more just something to keep in mind.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">publish-docker</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="l">...</span><span class="w">
</span></span></span></code></pre></div><p>Now we define our job, where <code>publish-docker</code> is the name of our job on <code>Gitlab CI</code> pipeline. We then define
what <code>stage</code> the job should run in, in this case, this job will run during the <code>publish</code> stage.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">publish-docker</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="l">...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">docker</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="l">...</span><span class="w">
</span></span></span></code></pre></div><p>Then we define what Docker image to use in this job. In this job, we will use the <code>docker</code> image. This
image has all the commands we need to <code>build</code> and <code>push</code> our Docker images. It will act as the client making
requests to the <code>dind</code> daemon.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">export VERSION_TAG=v1.2.3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">docker login ${CI_REGISTRY} -u gitlab-ci-token -p ${CI_BUILD_TOKEN}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">docker build -t ${CI_REGISTRY_IMAGE}:latest -t ${CI_REGISTRY_IMAGE}:${VERSION_TAG}  .</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">docker push ${CI_REGISTRY_IMAGE}:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">docker push ${CI_REGISTRY_IMAGE}:${VERSION_TAG}</span><span class="w">
</span></span></span></code></pre></div><p>Finally, we get to the real meat and potatoes of the CI file. The bit of code that builds and pushes are Docker
images to the registry:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="l">export VERSION_TAG=v1.2.3</span><span class="w">
</span></span></span></code></pre></div><p>It is often a good idea to tag our images, in this case, I&rsquo;m using a release name. You could get this from say your
<code>setup.py</code> or <code>package.json</code> file as well. In my Python projects I usually use this command
<code>export VERSION_TAG=$(cat setup.py | grep version | head -1 | awk -F= '{ print $2 }' | sed 's/[&quot;,]//g' | tr -d &quot;'&quot;)</code>,
to parse my <code>setup.py</code> for the version number. But this can be whatever you want it to be. Here we have just kept it
static to make things simpler but in reality, you&rsquo;ll probably want to retrieve it programmatically (the version number).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="l">docker login ${CI_REGISTRY} -u gitlab-ci-token -p ${CI_BUILD_TOKEN}</span><span class="w">
</span></span></span></code></pre></div><p>Then we log in to our Gitlab registry, the environment variables <code>$CI_REGISTRY</code> and <code>CI_BUILD_TOKEN</code> are predefined
Gitlab variables that are injected into our environment. You can read more about them
<a href="https://docs.gitlab.com/ee/ci/variables/predefined_variables.html">here</a>. Since we are pushing to our Gitlab registry
we can just use the credentials defined within environment i.e. <code>username=gitlab-ci-token</code> and password a throwaway
token.</p>
<blockquote>
<p>Note: You can only do this on protected branches/tags.</p>
</blockquote>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="l">docker build -t ${CI_REGISTRY_IMAGE}:latest -t ${CI_REGISTRY_IMAGE}:${VERSION_TAG}  .</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="l">docker push ${CI_REGISTRY_IMAGE}:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="l">docker push ${CI_REGISTRY_IMAGE}:${VERSION_TAG}</span><span class="w">
</span></span></span></code></pre></div><p>Finally, we run our normal commands to build and push our images. The place where you can find your images will depend
on the project name and your username but it should follow this format</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">registry.gitlab.com/&lt;username&gt;/&lt;project_name&gt;/&lt;tag&gt;
</span></span></code></pre></div><h3 id="optional-push-to-dockerhub">(Optional) Push to DockerHub</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="l">docker login -u hmajid2301 -p ${DOCKER_PASSWORD}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="l">export IMAGE_NAME=&#34;hmajid2301/example_project&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="l">docker build -t ${IMAGE_NAME}:latest -t ${IMAGE_NAME}:${VERSION_TAG}  .</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="l">docker push ${IMAGE_NAME}:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="l">docker push ${IMAGE_NAME}:${VERSION_TAG}</span><span class="w">
</span></span></span></code></pre></div><p>We can also push our images to DockerHub, with the code shown above. We need to first login to DockerHub. Then change
the name of our image <code>&lt;username&gt;/&lt;project_name&gt;</code>.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li>A good <a href="https://stackoverflow.com/questions/47280922/role-of-docker-in-docker-dind-service-in-gitlab-ci">Stackoverflow Post</a></li>
<li><a href="https://docs.gitlab.com/ee/ci/docker/using_docker_build.html">Gitlab CI Docs</a></li>
<li><a href="https://gitlab.com/gitlab-examples/docker/blob/master/.gitlab-ci.yml">Gitlab Example</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Using React Hooks, Context &amp; Local Storage</title>
      <link>https://haseebmajid.dev/posts/2020-04-05-using-react-hooks-context-local-storage/</link>
      <pubDate>Sun, 05 Apr 2020 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2020-04-05-using-react-hooks-context-local-storage/</guid>
      <description>&lt;p&gt;In this article, I will show how you can use React Context with React Hooks to store global state across a React app,
then store that state in local storage. This can be used for example to store light vs dark theme, then whenever the
user visits your website again they will have the same theme they last selected. Which leads to an improved experience.&lt;/p&gt;
&lt;h2 id=&#34;structure&#34;&gt;Structure&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: We will be using typescript&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this article, I will show how you can use React Context with React Hooks to store global state across a React app,
then store that state in local storage. This can be used for example to store light vs dark theme, then whenever the
user visits your website again they will have the same theme they last selected. Which leads to an improved experience.</p>
<h2 id="structure">Structure</h2>
<blockquote>
<p>Note: We will be using typescript</p>
</blockquote>
<p>We will use a project structure like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">.
</span></span><span class="line"><span class="cl">├── src
</span></span><span class="line"><span class="cl">│   ├── App.tsx
</span></span><span class="line"><span class="cl">│   ├── index.html
</span></span><span class="line"><span class="cl">│   ├── index.tsx
</span></span><span class="line"><span class="cl">│   ├── providers
</span></span><span class="line"><span class="cl">│   └── views
</span></span><span class="line"><span class="cl">├── LICENSE
</span></span><span class="line"><span class="cl">├── package.json
</span></span><span class="line"><span class="cl">├── tsconfig.json
</span></span><span class="line"><span class="cl">├── webpack.config.js
</span></span><span class="line"><span class="cl">└── yarn.lock
</span></span></code></pre></div><blockquote>
<p>Note: This application was based on <a href="https://github.com/saltyshiomix/webpack-typescript-react-starter">saltyshiomix&rsquo;s template</a></p>
</blockquote>
<h2 id="getting-started">Getting Started</h2>
<p>Our <code>package.json</code> file looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;ExampleApp&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;version&#34;</span><span class="p">:</span> <span class="s2">&#34;1.0.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;scripts&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;start&#34;</span><span class="p">:</span> <span class="s2">&#34;serve dist&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;dependencies&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;react&#34;</span><span class="p">:</span> <span class="s2">&#34;16.9.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;react-dom&#34;</span><span class="p">:</span> <span class="s2">&#34;16.9.0&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;devdependencies&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;typescript&#34;</span><span class="p">:</span> <span class="s2">&#34;3.6.2&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The example application linked will also be using babel for transpiling our code to Javascript
and Webpack for bundling our code into a single <code>index.js</code> file.</p>
<h2 id="app">App</h2>
<p>Now onto how we can use React Hooks to persist user settings in local storage. So every time they
visit our website it will &ldquo;restore&rdquo; their previous setting, such as theme, light or dark.</p>
<h3 id="darkmodeprovidertsx">DarkModeProvider.tsx</h3>
<p>React Contexts can be used to store the global state of our application. Such as our current theme, this can then be
accessed anywhere in our application and also changed anywhere. React contexts provide us with two &ldquo;sub-components&rdquo;, a
provider and, a consumer for that specific React context.</p>
<ul>
<li>Provider: The component that will provide the value of the context (stored)</li>
<li>Consumer: The component that will consume the value</li>
</ul>
<blockquote>
<p>Context provides a way to pass data through the component tree without having to pass props down manually at every level. - <a href="https://reactjs.org/docs/context.html">https://reactjs.org/docs/context.html</a></p>
</blockquote>
<p>React hooks allow us to access the React context from within functional components. In our case, it means we don&rsquo;t have
to use the React context&rsquo;s consumer we can use React hooks instead to use the context, this can be seen in the <code>MainApp.tsx</code></p>
<p>First, let&rsquo;s create our React context that will store the current theme the user has selected. It will also
give us a function that other components can use to update the theme. Finally, after any change has been made
it will update the local storage with the users latest settings.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span><span class="p">,</span> <span class="p">{</span> <span class="nx">Context</span><span class="p">,</span> <span class="nx">createContext</span><span class="p">,</span> <span class="nx">useReducer</span><span class="p">,</span> <span class="nx">useEffect</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;react&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="nx">LIGHT_THEME</span>: <span class="kt">Theme</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">background</span><span class="o">:</span> <span class="s2">&#34;#fafafa&#34;</span> <span class="kr">as</span> <span class="nx">BackgroundColors</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">color</span><span class="o">:</span> <span class="s2">&#34;#000000&#34;</span> <span class="kr">as</span> <span class="nx">ForegroundColors</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">isDark</span>: <span class="kt">false</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="nx">DARK_THEME</span>: <span class="kt">Theme</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">background</span><span class="o">:</span> <span class="s2">&#34;#333333&#34;</span> <span class="kr">as</span> <span class="nx">BackgroundColors</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">color</span><span class="o">:</span> <span class="s2">&#34;#fafafa&#34;</span> <span class="kr">as</span> <span class="nx">ForegroundColors</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">isDark</span>: <span class="kt">true</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">type</span> <span class="nx">BackgroundColors</span> <span class="o">=</span> <span class="s2">&#34;#333333&#34;</span> <span class="o">|</span> <span class="s2">&#34;#fafafa&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">type</span> <span class="nx">ForegroundColors</span> <span class="o">=</span> <span class="s2">&#34;#000000&#34;</span> <span class="o">|</span> <span class="s2">&#34;#fafafa&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">interface</span> <span class="nx">Theme</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">background</span>: <span class="kt">BackgroundColors</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">color</span>: <span class="kt">ForegroundColors</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">isDark</span>: <span class="kt">boolean</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">interface</span> <span class="nx">DarkModeContext</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">mode</span>: <span class="kt">Theme</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">dispatch</span>: <span class="kt">React.Dispatch</span><span class="p">&lt;</span><span class="nt">any</span><span class="p">&gt;;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">darkModeReducer</span> <span class="o">=</span> <span class="p">(</span><span class="nx">_</span>: <span class="kt">any</span><span class="p">,</span> <span class="nx">isDark</span>: <span class="kt">boolean</span><span class="p">)</span> <span class="o">=&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">isDark</span> <span class="o">?</span> <span class="nx">DARK_THEME</span> : <span class="kt">LIGHT_THEME</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">DarkModeContext</span>: <span class="kt">Context</span><span class="p">&lt;</span><span class="nt">DarkModeContext</span><span class="p">&gt;</span> <span class="o">=</span> <span class="nx">createContext</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="p">{}</span> <span class="kr">as</span> <span class="nx">DarkModeContext</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">initialState</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl">  <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">localStorage</span><span class="p">.</span><span class="nx">getItem</span><span class="p">(</span><span class="s2">&#34;DarkMode&#34;</span><span class="p">)</span> <span class="kr">as</span> <span class="kt">string</span><span class="p">)</span> <span class="o">||</span> <span class="nx">LIGHT_THEME</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">DarkModeProvider</span>: <span class="kt">React.FC</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">children</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="p">[</span><span class="nx">mode</span><span class="p">,</span> <span class="nx">dispatch</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useReducer</span><span class="p">(</span><span class="nx">darkModeReducer</span><span class="p">,</span> <span class="nx">initialState</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">useEffect</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">localStorage</span><span class="p">.</span><span class="nx">setItem</span><span class="p">(</span><span class="s2">&#34;DarkMode&#34;</span><span class="p">,</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">mode</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span> <span class="p">[</span><span class="nx">mode</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">DarkModeContext.Provider</span>
</span></span><span class="line"><span class="cl">      <span class="na">value</span><span class="o">=</span><span class="p">{{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">mode</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">dispatch</span>
</span></span><span class="line"><span class="cl">      <span class="p">}}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span><span class="nx">children</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">DarkModeContext.Provider</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="p">{</span> <span class="nx">DarkModeProvider</span><span class="p">,</span> <span class="nx">DarkModeContext</span> <span class="p">};</span>
</span></span></code></pre></div><p>Next, we will import all of the modules we will need to use then. We will define our two different themes <code>LIGHT_THEME</code>
and <code>DARK_THEME</code>. Then finally because we are using Typescript we will define types for the Themes and the context we
will use.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">darkModeReducer</span> <span class="o">=</span> <span class="p">(</span><span class="nx">_</span>: <span class="kt">any</span><span class="p">,</span> <span class="nx">isDark</span>: <span class="kt">boolean</span><span class="p">)</span> <span class="o">=&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">isDark</span> <span class="o">?</span> <span class="nx">DARK_THEME</span> : <span class="kt">LIGHT_THEME</span><span class="p">;</span>
</span></span></code></pre></div><p>Next, we will define a reducer. A reducer is a pure function which does not use the state of the
current app so it cannot have any unintended side-effects. Exactly the same functions we
would define if we were using Redux. In this case, the reducer just returns the <code>DARK_THEME</code>
if the <code>isDark</code> argument is <code>true</code> else it returns the <code>LIGHT_THEME</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">DarkModeContext</span>: <span class="kt">Context</span><span class="p">&lt;</span><span class="nt">DarkModeContext</span><span class="p">&gt;</span> <span class="o">=</span> <span class="nx">createContext</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="p">{}</span> <span class="kr">as</span> <span class="nx">DarkModeContext</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">initialState</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl">  <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">localStorage</span><span class="p">.</span><span class="nx">getItem</span><span class="p">(</span><span class="s2">&#34;DarkMode&#34;</span><span class="p">)</span> <span class="kr">as</span> <span class="kt">string</span><span class="p">)</span> <span class="o">||</span> <span class="nx">LIGHT_THEME</span><span class="p">;</span>
</span></span></code></pre></div><p>After this, we create our React context called <code>DarkModeContext</code> and we give it a default empty object
(we don&rsquo;t really mind too much). We then define the default value. It tries to check the value
stored in <code>localstorage</code>. If there is none, then we use the <code>LIGHT_THEME</code>. After which we define the provider.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">DarkModeProvider</span>: <span class="kt">React.FC</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">children</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="p">[</span><span class="nx">mode</span><span class="p">,</span> <span class="nx">dispatch</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useReducer</span><span class="p">(</span><span class="nx">darkModeReducer</span><span class="p">,</span> <span class="nx">initialState</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">useEffect</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">localStorage</span><span class="p">.</span><span class="nx">setItem</span><span class="p">(</span><span class="s2">&#34;DarkMode&#34;</span><span class="p">,</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">mode</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span> <span class="p">[</span><span class="nx">mode</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">DarkModeContext.Provider</span>
</span></span><span class="line"><span class="cl">      <span class="na">value</span><span class="o">=</span><span class="p">{{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">mode</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">dispatch</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="p">}}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span><span class="nx">children</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">DarkModeContext.Provider</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="p">{</span> <span class="nx">DarkModeProvider</span><span class="p">,</span> <span class="nx">DarkModeContext</span> <span class="p">};</span>
</span></span></code></pre></div><p>The provider is what is used to give other components access to the context. Here you can see
we use the <code>useReducer</code> hook and give it our <code>darkModeReducer</code> with the initial value. This
reducer will then return a <code>mode</code> which is the current theme data and a function <code>dispatch</code>
which will be used to update the current theme. Breaking it down a bit further we see:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="nx">useEffect</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">localStorage</span><span class="p">.</span><span class="nx">setItem</span><span class="p">(</span><span class="s2">&#34;DarkMode&#34;</span><span class="p">,</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">mode</span><span class="p">));</span>
</span></span><span class="line"><span class="cl"><span class="p">},</span> <span class="p">[</span><span class="nx">mode</span><span class="p">]);</span>
</span></span></code></pre></div><p>Next, we define the <code>useEffect</code> hook which is called every time the <code>mode</code> is changed, by the
<code>dispatch</code> function being called. Hence the we have the <code>[mode]</code> at the end. It very simply
stores the current theme into the user&rsquo;s local storage under the key <code>DarkMode</code>. Now if
this was changed from light -&gt; dark and then the user comes back to the site, the initial value
we would get from <code>localstorage.getItem(&quot;DarkMode&quot;)</code> would not, of course, be the dark theme.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">DarkModeContext.Provider</span>
</span></span><span class="line"><span class="cl">    <span class="na">value</span><span class="o">=</span><span class="p">{{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">mode</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">dispatch</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">}}</span>
</span></span><span class="line"><span class="cl">  <span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="nx">children</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">DarkModeContext.Provider</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">//...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">export</span> <span class="p">{</span> <span class="nx">DarkModeProvider</span><span class="p">,</span> <span class="nx">DarkModeContext</span> <span class="p">};</span>
</span></span></code></pre></div><p>Finally, we create the Provider component we will export, the <code>mode</code> is the theme data that other
components can use and <code>dispatch</code> is the function other components can use to change the current
theme. As long as they are a child of the <code>DarkModeProvider</code> hence the <code>{children}</code> which will be a prop.</p>
<h3 id="app-1">App</h3>
<p>Our &ldquo;Main&rdquo; app page we will import the Provider that will export from our providers folder.
This means any component that is a child of this will be able to access and update the current
theme, we will see how to do that later on.</p>
<blockquote>
<p>Warning: The provider needs to be in a separate component to those that access the React Hook. Hence we import the <code>MainApp</code> component rather than including all of the <code>MainApp.tsx</code> in <code>App.tsx</code>.</p>
</blockquote>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span> <span class="kr">from</span> <span class="s2">&#34;react&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">DarkModeProvider</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;~/providers/DarkModeProvider&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">MainApp</span> <span class="kr">from</span> <span class="s2">&#34;~/views/MainApp&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">App</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">DarkModeProvider</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">MainApp</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">DarkModeProvider</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">App</span><span class="p">;</span>
</span></span></code></pre></div><blockquote>
<p>Note: The module resolver allows us to refer to src/ folder as ~ in our imports. I wrote a whole article about how you can use it <a href="/blog/better-imports-with-babel-tspath/">here</a> (#ShamelessPlug)</p>
</blockquote>
<p>Now the MainApp is a very basic page: it contains a single button which is used to toggle our theme
for dark to light and vice versa. Here we use React hooks with React context to be able to update and retrieve
the theme.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span><span class="p">,</span> <span class="p">{</span> <span class="nx">useContext</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;react&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">DarkModeContext</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;~/providers/DarkModeProvider&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">MainApp</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">theme</span> <span class="o">=</span> <span class="nx">useContext</span><span class="p">(</span><span class="nx">DarkModeContext</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="p">{</span> <span class="nx">background</span><span class="p">,</span> <span class="nx">color</span><span class="p">,</span> <span class="nx">isDark</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">theme</span><span class="p">.</span><span class="nx">mode</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">div</span>
</span></span><span class="line"><span class="cl">      <span class="na">style</span><span class="o">=</span><span class="p">{{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">background</span>: <span class="kt">background</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">color</span>: <span class="kt">color</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">minHeight</span><span class="o">:</span> <span class="s2">&#34;100vh&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">}}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span><span class="nx">Theme</span> <span class="k">is</span> <span class="p">{</span><span class="nx">isDark</span> <span class="o">?</span> <span class="s2">&#34;Dark&#34;</span> <span class="o">:</span> <span class="s2">&#34;Light&#34;</span><span class="p">}&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">button</span> <span class="na">onClick</span><span class="o">=</span><span class="p">{()</span> <span class="o">=&gt;</span> <span class="nx">setTheme</span><span class="p">(</span><span class="nx">theme</span><span class="p">)}&gt;</span><span class="nx">Change</span> <span class="nx">Theme</span><span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">setTheme</span> <span class="o">=</span> <span class="p">(</span><span class="nx">darkMode</span>: <span class="kt">DarkModeContext</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">isDark</span> <span class="o">=</span> <span class="nx">darkMode</span><span class="p">.</span><span class="nx">mode</span><span class="p">.</span><span class="nx">isDark</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">darkMode</span><span class="p">.</span><span class="nx">dispatch</span><span class="p">(</span><span class="o">!</span><span class="nx">isDark</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">MainApp</span><span class="p">;</span>
</span></span></code></pre></div><h4 id="usecontext">useContext</h4>
<p>The <code>useContext</code> is an example of a React Hook. It allows users to access a specific context from with a functional
component, a component which is not a class. The context has a mode property which stores the current theme we should
display light or dark. Such as <code>background</code> and <code>color</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">theme</span> <span class="o">=</span> <span class="nx">useContext</span><span class="p">(</span><span class="nx">DarkModeContext</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="p">{</span> <span class="nx">background</span><span class="p">,</span> <span class="nx">color</span><span class="p">,</span> <span class="nx">isDark</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">theme</span><span class="p">.</span><span class="nx">mode</span><span class="p">;</span>
</span></span></code></pre></div><p>This is then used in our &ldquo;CSS&rdquo; styling to style the page background and button colour. We also show the current theme
that is set on the page.</p>
<h4 id="change-theme">Change Theme</h4>
<p>So we can access the data from our React context but how do we change the theme? Well, we use the button, which
has an <code>onClick</code> event. The <code>setTheme</code> function gets the current theme from the <code>isDark</code> property of the context.
It then calls the <code>dispatch</code> function we have defined in the context to change to the theme to the opposite
it is at the moment. So light theme -&gt; dark theme and dark theme -&gt; light theme.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">button</span> <span class="na">onClick</span><span class="o">=</span><span class="p">{()</span> <span class="o">=&gt;</span> <span class="nx">setTheme</span><span class="p">(</span><span class="nx">theme</span><span class="p">)}&gt;</span><span class="nx">Change</span> <span class="nx">Theme</span><span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">//...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">setTheme</span> <span class="o">=</span> <span class="p">(</span><span class="nx">darkMode</span>: <span class="kt">DarkModeContext</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">isDark</span> <span class="o">=</span> <span class="nx">darkMode</span><span class="p">.</span><span class="nx">mode</span><span class="p">.</span><span class="nx">isDark</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">darkMode</span><span class="p">.</span><span class="nx">dispatch</span><span class="p">(</span><span class="o">!</span><span class="nx">isDark</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>That&rsquo;s it! We successfully created a very simple React app that leverage React hooks and React context to allow us
to store the user&rsquo;s settings into local storage so it can persist and the user will be able to use the same settings
they set last time, such as dark mode instead of the light mode.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2020-04-05-using-react-hooks-context-local-storage/source_code">Example source code</a></li>
<li>Photo by <a href="https://unsplash.com/@cristianpalmer">Cristian Palmer</a> on Unsplash</li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Deploy Docz on Gitlab Pages</title>
      <link>https://haseebmajid.dev/posts/2020-03-28-how-to-deploy-docz-on-gitlab-pages/</link>
      <pubDate>Sat, 28 Mar 2020 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2020-03-28-how-to-deploy-docz-on-gitlab-pages/</guid>
      <description>&lt;p&gt;In this article I will show you how you can deploy a Docz website on Gitlab pages, using &lt;code&gt;.gitlab-ci.yml&lt;/code&gt;.
Most of this article should be applicable to Github pages as well.&lt;/p&gt;
&lt;h2 id=&#34;docz&#34;&gt;Docz&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://www.Docz.site/&#34;&gt;Docz&lt;/a&gt; is a tool powered by Gatsby, it aims to make it easier to document your project.
It uses a language called &lt;code&gt;mdx&lt;/code&gt; which is like normal markdown with some extra features, i.e. &lt;code&gt;md + jsx&lt;/code&gt;. The main
advantage of using Docz is you can render components &amp;ldquo;live&amp;rdquo;, if you put them with the &lt;code&gt;&amp;lt;playground&amp;gt;&lt;/code&gt; tags. A basic
example may look like this:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this article I will show you how you can deploy a Docz website on Gitlab pages, using <code>.gitlab-ci.yml</code>.
Most of this article should be applicable to Github pages as well.</p>
<h2 id="docz">Docz</h2>
<p><a href="https://www.Docz.site/">Docz</a> is a tool powered by Gatsby, it aims to make it easier to document your project.
It uses a language called <code>mdx</code> which is like normal markdown with some extra features, i.e. <code>md + jsx</code>. The main
advantage of using Docz is you can render components &ldquo;live&rdquo;, if you put them with the <code>&lt;playground&gt;</code> tags. A basic
example may look like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl">---
</span></span><span class="line"><span class="cl">name: Button
</span></span><span class="line"><span class="cl">route: /
</span></span><span class="line"><span class="cl">---
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">import { Playground, Props } from &#39;Docz&#39;
</span></span><span class="line"><span class="cl">import { Button } from &#39;./&#39;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gh"># Button
</span></span></span><span class="line"><span class="cl"><span class="gh"></span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">Props</span> <span class="na">of</span><span class="o">=</span><span class="s">{Button}</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">## Basic usage
</span></span></span><span class="line"><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">Playground</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">Button</span><span class="p">&gt;</span>Click me<span class="p">&lt;/</span><span class="nt">Button</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">Button</span> <span class="na">kind</span><span class="o">=</span><span class="s">&#34;secondary&#34;</span><span class="p">&gt;</span>Click me<span class="p">&lt;/</span><span class="nt">Button</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">Playground</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>I chose to use Docz because it was simple to set up and looks very nice. I was already writing my documentation
in markdown so it seemed like a perfect fit, even though in my use case I didn&rsquo;t use the <code>playground</code> to render
components.</p>
<h2 id="example">Example</h2>
<p>You can find an example project using <a href="https://gitlab.com/hmajid2301/stegappasaurus/-/tree/release/1.0.2/">Docz here</a>.
This is one of my projects where I deployed the documentation using Gitlab Pages. You can
find <a href="https://stegappasaurus.haseebmajid.dev/">it here</a>.</p>
<h2 id="getting-started">Getting Started</h2>
<p>Ok, now let&rsquo;s get into how we can add Docz to an existing project. We also need to have <code>react-dom</code> and <code>react</code>
installed.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">yarn add Docz
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># or</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">npm install Docz
</span></span></code></pre></div><p>So our <code>package.json</code> looks like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;example_app&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;scripts&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;docs-dev&#34;</span><span class="p">:</span> <span class="s2">&#34;docz dev&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;docs-build&#34;</span><span class="p">:</span> <span class="s2">&#34;docz build&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;dependencies&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;Docz&#34;</span><span class="p">:</span> <span class="s2">&#34;2.2.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;react&#34;</span><span class="p">:</span> <span class="s2">&#34;16.9.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;react-dom&#34;</span><span class="p">:</span> <span class="s2">&#34;16.8.0&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>We then need to create our <code>Doczrc.js</code> configuration file, like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">src</span><span class="o">:</span> <span class="s2">&#34;docs&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">description</span><span class="o">:</span> <span class="s2">&#34;Example Documentation&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">menu</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;Introduction&#34;</span><span class="p">,</span> <span class="s2">&#34;Contributing&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nx">themeConfig</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">initialColorMode</span><span class="o">:</span> <span class="s2">&#34;dark&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>We will keep all our <code>.mdx</code> files in a folder called <code>docs</code> hence <code>src: 'docs'</code>. Each file will be shown as a page on
our website. The <code>description</code> option will be the name of our website in an open browser tab. The <code>menu</code> option is
the order in which our pages will show up in the sidebar (on the left-hand side by default). The names used in this
menu option must match the names used in the frontmatter of that file should match the front matter in <code>mdx</code> pages.
Finally, I want to use the <code>dark</code> mode by default which is the final option.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl">---
</span></span><span class="line"><span class="cl">name: Button
</span></span><span class="line"><span class="cl">route: /
</span></span><span class="line"><span class="cl">---
</span></span></code></pre></div><p>We can view our current <code>docz</code> website by running <code>docs-dev</code> and then go to <code>localhost:3000</code> on our dev machine.</p>
<h3 id="adding-pages">Adding Pages</h3>
<p>Ok, now let&rsquo;s add our actual &ldquo;pages&rdquo; to our Docz website. First create a new folder called <code>docs</code> in your project root.
Then we will create our first page called <code>Introduction.mdx</code> (this name doesn&rsquo;t matter so much), where the page&rsquo;s contents
look something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl">---
</span></span><span class="line"><span class="cl">name: Introduction
</span></span><span class="line"><span class="cl">route: /
</span></span><span class="line"><span class="cl">---
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gh"># Example
</span></span></span><span class="line"><span class="cl"><span class="gh"></span>
</span></span><span class="line"><span class="cl">Welcome to this example app, in this app we will show examples.
</span></span></code></pre></div><blockquote>
<p>Note: The front matter <code>name</code> must match the name we defined in the <code>Doczrc.js</code> menu option.</p>
</blockquote>
<p>The route defines the path the user will see, i.e. in the <code>stegappasaurus</code> example this page will be shown on
<code>https://stegappasaurus.haseebmajid.dev/</code>.</p>
<p>Next let&rsquo;s create a second page called <code>Contributing.mdx</code>, which looks like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl">---
</span></span><span class="line"><span class="cl">name: Contributing
</span></span><span class="line"><span class="cl">route: /contributing
</span></span><span class="line"><span class="cl">---
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gh"># Contributing
</span></span></span><span class="line"><span class="cl"><span class="gh"></span>
</span></span><span class="line"><span class="cl">Three main ways to contribute to this project are;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">-</span> **Adding a new feature:** Adding a new feature to the project, such as allow encoding of audio files alongside images
</span></span><span class="line"><span class="cl"><span class="k">-</span> **Improving a feature:** Extend/improve an existing feature, such as a small UI change
</span></span><span class="line"><span class="cl"><span class="k">-</span> **Fix an issue:** We have a list of [<span class="nt">issues</span>](<span class="na">https://gitlab.com/hmajid2301/stegappasaurus/issues</span>), or you can fix your issue.
</span></span></code></pre></div><p>This page can be found on <code>/contributing</code> i.e. <code>https://stegappasaurus.haseebmajid.dev/contributing</code>. The page may look
something like the image below. The titles are shown as sub-menus.</p>
<p><img
        loading="lazy"
        src="/posts/2020-03-28-how-to-deploy-docz-on-gitlab-pages/images/docs.png"
        type=""
        alt="Example App"
        
      /></p>
<h2 id="gitlab-pages">Gitlab Pages</h2>
<p>Now that we have our Docz website and it is working locally, how can we deploy on Gitlab pages for all the world to see?
Well first we need to add a job titled <code>pages</code> to our <code>.gitlab-ci.yml</code> file, then we need to store all of our
static assets in a folder called <code>public</code> and make that an <code>artifact</code> of this job. This will tell Gitlab CI, that we
want to publish this &ldquo;website&rdquo; to Gitlab Pages. Here is an example of what it may look like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">pages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">master</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn docs-build</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">mv .Docz/dist/* public/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">artifacts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">public</span><span class="w">
</span></span></span></code></pre></div><p>This will publish our &ldquo;website&rdquo; to https://<username>.gitlab.io/&lt;project_name&gt;. So, for example, my username is
<code>hmajid2301</code> and my project name is <code>stegappasaurus</code> hence the website URL is <a href="https://hmajid2301.gitlab.io/stegappasaurus">https://hmajid2301.gitlab.io/stegappasaurus</a>.</p>
<h3 id="custom-domain-optional">Custom Domain (Optional)</h3>
<p>If you have your own custom domain, you can &ldquo;host&rdquo; the page under two URLs. In my case, I own <code>haseebmajid.dev</code> and I
wanted to host it under a subdomain within that domain, like so <code>https://stegappasaurus.haseebmajid.dev/</code>. Gitlab
makes this surprisingly easy to do:</p>
<blockquote>
<p>Note: In this example, I am assuming we are using a subdomain and not the root domain i.e. example_app.haseebmajid.dev not haseebmajid.dev.</p>
</blockquote>
<ul>
<li>First go to your project on Gitlab</li>
<li>Next Settings (Left sidebar) &gt; Pages &gt; New Domain (Top Righthand Corner)</li>
<li>Enter the domain name you&rsquo;d like to use, i.e. example_app.haseebmajid.dev</li>
<li>Press <code>Create New Domain</code></li>
<li>Copy the <code>TXT</code> record to verify ownership.</li>
<li>Next go your domain provider, in my case it&rsquo;s <a href="https://domains.google.com">Google</a></li>
<li>Go to your DNS settings for that domain</li>
</ul>
<p>We will now create two DNS records a TXT record which verifies ownership of the domain. Go to settings for &ldquo;pages&rdquo;
which can be found <code>https://gitlab.com/&lt;username&gt;/&lt;project_name&gt;/pages</code>. Then copy the data next to
<code>Verification status</code> into your DNS settings.</p>
<p>Next we will create a <code>CNAME</code> record, a CNAME is used to point one domain record to another, i.e. a user comes to
<code>stegappasaurus.haseebmajid.dev CNAME -&gt; hmajid2301.gitlab.io -&gt; Resolve IP Address</code>. Then the browser will take the
user to the correct IP address. You can copy the CNAME data next to the <code>DNS</code> field.</p>
<p>After we&rsquo;ve added the details it will take a few minutes for verification and for <code>Let's encrypt</code> to create a certificate
for our website. Then you should be able to view your Docz website using both domains listed under the page settings
of your project, i.e the <code>gitlab.io</code> url and your custom domain.</p>
<p>You can find more information
<a href="https://docs.gitlab.com/ee/user/project/pages/custom_domains_ssl_tls_certification/#3-set-up-dns-records-for-pages">here</a>,
with regards to Gitlab pages and custom domains.</p>
<blockquote>
<p>Note: <code>.dev</code> domains always needs to be HTTPS encrypted (need a certificate).</p>
</blockquote>
<blockquote>
<p>Note: For Google Domains this data must be added in the DNS &gt; Custom resource records.</p>
</blockquote>
<p><img
        loading="lazy"
        src="/posts/2020-03-28-how-to-deploy-docz-on-gitlab-pages/images/pages.png"
        type=""
        alt="pages"
        
      /></p>
<p>That&rsquo;s it. We have deployed a <code>docz</code> website using Gitlab CI onto Gitlab Pages and even added our custom domain to it.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/stegappasaurus/-/tree/release/1.0.2">Example Project</a></li>
<li><a href="https://docs.gitlab.com/ee/user/project/pages/custom_domains_ssl_tls_certification/#3-set-up-dns-records-for-pages">Gitlab Tutorial</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>An Example Gitlab CI file for React Native Apps</title>
      <link>https://haseebmajid.dev/posts/2020-02-23-an-example-gitlab-ci-file-for-react-native-apps/</link>
      <pubDate>Sun, 23 Feb 2020 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2020-02-23-an-example-gitlab-ci-file-for-react-native-apps/</guid>
      <description>&lt;p&gt;A bit of backstory when I first started developing React Native apps, I found there weren&amp;rsquo;t
any good example of Gitlab CI files. So in this article, I will show you an example &lt;code&gt;.gitlab-ci.yml&lt;/code&gt;
file you can use with your React Native app. You can of course tweak and makes changes as required by your
project.&lt;/p&gt;
&lt;h2 id=&#34;cicd&#34;&gt;CI/CD&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Before we dive straight into the CI file itself, let&amp;rsquo;s do a quicker refresher on some basic concepts. Feel free to skip this section if you are already familiar with CI/CD, Git and Gitlab CI.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>A bit of backstory when I first started developing React Native apps, I found there weren&rsquo;t
any good example of Gitlab CI files. So in this article, I will show you an example <code>.gitlab-ci.yml</code>
file you can use with your React Native app. You can of course tweak and makes changes as required by your
project.</p>
<h2 id="cicd">CI/CD</h2>
<blockquote>
<p>Before we dive straight into the CI file itself, let&rsquo;s do a quicker refresher on some basic concepts. Feel free to skip this section if you are already familiar with CI/CD, Git and Gitlab CI.</p>
</blockquote>
<p>Continuous Integration (CI), is typically defined as making sure all code being integrated into a codebase works.
It usually involves running a set of jobs referred to as a CI pipeline. Some jobs we may run include linting our
code and running unit tests. This is usually done automatically using a tool such as Travis, Circle or even Gitlab.</p>
<p>One particularly useful use case for this is when others are adding new features to our codebase and we want to check it
still works. We can create a CI pipeline that will run unit tests against the new code automatically when a pull request
(GitHub) or merge request (Gitlab) is opened. This saves us a lot of time, rather than having to copy the new
features/code and then run the tests our selves on our machine.</p>
<p>Continuous Delivery (CD), is typically an extension of CI to make sure that you can release new changes quickly.
This means automating your release process, such that you can deploy your application at any point of time just
by clicking on a button.</p>
<p>Continuous Deployment takes CD one step further by requiring no human intervention in deploying our application.
You can read more about <a href="https://www.atlassian.com/continuous-delivery/principles/continuous-integration-vs-delivery-vs-deployment">this here</a>.</p>
<h3 id="git">Git</h3>
<p><a href="https://guides.github.com/introduction/git-handbook/">Git</a> is a version control system (VCS), it is heavily tied in with CI.
In git, we can make &ldquo;commits&rdquo; which are snapshots of our project at its current state. We can later revert back to older commits and
compare files between commits (and much more). Usually every commit we push to Gitlab triggers a CI pipeline run against that current commit.
Git also has this concept of branches, where usually the <code>master</code> branch contains our production-ready code and the other branches have
new features being worked on. When our feature branches are ready they are merged into the master branch. Usually, the CI pipeline needs
to be successfully running (green ticks) before this can happen, however.</p>
<h3 id="gitlab-ci">Gitlab CI</h3>
<p><a href="https://docs.gitlab.com/ee/ci/">Gitlab CI</a>, is defined as a YAML file. In the file, we define &ldquo;jobs&rdquo; which can do various
different task. You can read more <a href="https://docs.gitlab.com/ee/user/project/pages/getting_started_part_four.html">here</a>.
Full <a href="https://docs.gitlab.com/ee/ci/yaml/README.html">reference docs here</a>, which details all the different parameters we
can use. To use Gitlab CI within our projects is very straight forward, create a new file <code>.gitlab-ci.yml</code> in our project
root and then define our jobs (we will see this a bit later in the article).</p>
<h3 id="example">Example</h3>
<p>
  <img
    loading="lazy"
    src="https://docs.gitlab.com/ee/ci/introduction/img/gitlab_workflow_example_11_9.png"
    alt="https://docs.gitlab.com/ee/ci/introduction/"
    
  /></p>
<p>The image above shows an example of a workflow we may use. So we create a new branch for our feature called <code>feature/add-x</code>.
We then create our commits (with our new code) and push them to Gitlab. Open a merge request, this triggers the CI
pipeline (from the <code>.gitlab-ci.yml</code>) file. In this example, the pipeline fails, perhaps because a unit test failed. This
causes the whole pipeline to fail.</p>
<p>We then fix our code so the unit tests pass and then create more commits and push them. This then triggers the
pipeline to run again, this time it passes. Now our code is ready to be reviewed and merged into the main branch.
After the code review, it will be merged onto the <code>master</code> (main) branch. Then we will trigger the deployment process,
this can also be defined within our CI file.</p>
<h2 id="gitlab-ciyml">.gitlab-ci.yml</h2>
<p>Now onto the real meat and potatoes of this article, our <code>.gitlab-ci.yml</code> file for React Native apps.
Taking a look at an <a href="https://gitlab.com/hmajid2301/stegappasaurus">example application</a>. You can find the <code>.gitlab-ci.yml</code>
and <code>package.json</code> in the appendix below or follow the link above. Now let&rsquo;s take a look the <code>.gitlab-ci.yml</code> file.</p>
<h3 id="setup">setup</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">node:8</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">pre</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">post</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">cache</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">${CI_COMMIT_REF_SLUG}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">node_modules/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">DOCKER_DRIVER</span><span class="p">:</span><span class="w"> </span><span class="l">overlay2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">before_script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">yarn generate-dotenv</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">yarn</span><span class="w">
</span></span></span></code></pre></div><p>First, we specify an <code>image</code>, this is the default docker image we will use for our &ldquo;jobs&rdquo;.
Unless a job specifies an image explicitly in its definition it will use this one. In this
example, we will use <code>node 8</code> it already has node, npm and yarn installed. We could probably
upgrade this to <code>node 10</code> or even <code>node 12</code> (long term releases of node).</p>
<p>Next, we define all the <code>stages</code> of our pipeline, any jobs in the same stage will run
in parallel (at the same time). If a job in an earlier stage fails the pipeline won&rsquo;t carry
on to the next and will stop running at the current stage. The stages defined first such
as <code>pre</code> and <code>test</code> run before stages defined later such as <code>publish</code>.
Each job <strong>must</strong> be given a <code>stage</code>.</p>
<p>Next, we define a <code>cache</code>, we will cache the <code>node_modules</code> for
future jobs (in this pipeline). Gitlab CI injects some
<a href="https://docs.gitlab.com/ee/ci/variables/predefined_variables.html">predefined environment variables</a>,
one of them being <code>CI_COMMIT_REF_SLUG</code>.</p>
<p>We then define a variable <code>DOCKER_DRIVER: overlay2</code>, this helps speed our
docker containers a bit because by default it uses <code>vfs</code> which is slower
<a href="https://docs.gitlab.com/ce/ci/docker/using_docker_build.html#using-the-overlayfs-driver">learn more here</a>.</p>
<p>Finally, we define <code>before_script</code> which will run before every job unless we specify a
<code>before_script</code> within the jobs themselves. In this example, we install our node_modules
using <code>yarn</code> and create a <code>.env</code> file, we need the <code>.env</code> file for a few our the jobs.
The <code>.env</code> file is used by React Native to set configuration within the app.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;stegappasaurus&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;scripts&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;generate-dotenv&#34;</span><span class="p">:</span> <span class="s2">&#34;sh util/generate-dotenv.sh &gt; .env&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Where <code>BUGSNAG_API_KEY</code> and <code>CAT_API_KEY</code> are environment variables which are injected by
Gitlab <a href="https://docs.gitlab.com/ee/ci/variables/">more information here</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env bash
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl">cat  <span class="s">&lt;&lt; EOF
</span></span></span><span class="line"><span class="cl"><span class="s">BUGSNAG_API_KEY=${BUGSNAG_API_KEY}
</span></span></span><span class="line"><span class="cl"><span class="s">CAT_API_KEY=${CAT_API_KEY}
</span></span></span><span class="line"><span class="cl"><span class="s">EOF</span>
</span></span></code></pre></div><p>Where the generated <code>.env</code> file will look like.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">BUGSNAG_API_KEY</span><span class="o">=</span><span class="m">1232541</span>
</span></span><span class="line"><span class="cl"><span class="nv">CAT_API_KEY</span><span class="o">=</span>abxc-71379991
</span></span></code></pre></div><h3 id="jobs">jobs</h3>
<p><strong>Note:</strong> For the example application I am showing it has two branches <code>production</code> (main) and <code>master</code>.</p>
<h4 id="pre">pre</h4>
<p>Now, let&rsquo;s take a look at our jobs in the CI file. The first job is used to close
issues automatically on Gitlab if there is an issue number in the git commit.
It uses the following tool
<a href="https://gitlab.com/gitlab-automation-toolkit/gitlab-auto-close-issue">gitlab-auto-close-issue</a>.
Which provides a docker image which contains the script to auto-close your issues. It will also
remove labels from the issue if you want such as &ldquo;Doing&rdquo;.
This job is only run on the master branch of our project.</p>
<p>Since we don&rsquo;t need to install any dependencies to run the job <code>before_script: []</code> is an empty
list, therefore the default <code>before_script</code> defined above won&rsquo;t run in this job. Also since
we define a docker image within the job we don&rsquo;t use the default docker image <code>node:8</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">close:issue</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">registry.gitlab.com/gitlab-automation-toolkit/gitlab-auto-close-issue</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">pre</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">before_script</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">master</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">apk add --no-cache --upgrade grep</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">ISSUE=$(echo $CI_COMMIT_MESSAGE | grep -oP &#34;(?&lt;=Fixes \#)[0-9]+&#34; || echo &#39;1&#39;)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">gitlab_auto_close_issue --issue $ISSUE --remove-label &#34;Doing&#34; --remove-label &#34;To Do&#34;</span><span class="w">
</span></span></span></code></pre></div><p>The next job automatically creates a merge request (MR) if the commits are not being pushed to <code>master</code> or <code>production</code> branches.
It will create an MR as WIP with a template we defined in the <code>.gitlab</code> folder. We also set the option <code>--use-issue-name</code>
where if we have a branch called say <code>feature/#211</code> where <code>#211</code> is an issue number (for that project). It will take
certain bits of information from that issue and set it on the MR such as <code>labels</code>.
More information about the tool <a href="https://gitlab.com/gitlab-automation-toolkit/gitlab-auto-mr">gitlab-auto-mr</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">create:merge-request</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">registry.gitlab.com/gitlab-automation-toolkit/gitlab-auto-mr</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">pre</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">before_script</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">except</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">production</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">master</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">tags</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">gitlab_auto_mr -t master -c WIP -d .gitlab/merge_request_templates/merge_request.md -r -s --use-issue-name</span><span class="w">
</span></span></span></code></pre></div><p>Where the template could look something like this.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl"><span class="gh"># Description
</span></span></span><span class="line"><span class="cl"><span class="gh"></span>
</span></span><span class="line"><span class="cl"><span class="c">&lt;!-- please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. --&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">## Type
</span></span></span><span class="line"><span class="cl"><span class="gu"></span>
</span></span><span class="line"><span class="cl"><span class="k">- [ ]</span> Bug Fix
</span></span><span class="line"><span class="cl"><span class="k">- [ ]</span> Improvement
</span></span><span class="line"><span class="cl"><span class="k">- [ ]</span> New Feature
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Fixes #<span class="c">&lt;!-- Issue Number --&gt;</span>
</span></span></code></pre></div><h3 id="test">test</h3>
<p>This job called <code>lint</code> only runs on MRs not on the master branch i.e. it won&rsquo;t run if create an MR from master to production.
Hence the <code>except</code> clause. Finally we run the <code>lint</code> command which is defined in our <code>package.json</code> file as <code>eslint src/**/*.{ts,tx,tsx}</code>.
This will run eslint against all of the code within our <code>src</code> folder.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">lint</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_requests</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">except</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">$CI_COMMIT_REF_NAME =~ /^master/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn lint</span><span class="w">
</span></span></span></code></pre></div><p>Then <code>lint:code-formatter</code> checks our code against <code>prettier</code> and see&rsquo;s if it&rsquo;s compliant with the code formatter.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">lint:code-formatter</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_requests</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">except</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">$CI_COMMIT_REF_NAME =~ /^master/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn code-formatter-check</span><span class="w">
</span></span></span></code></pre></div><p>Then we check all of our TS is valid, by running <code>tsc --project . --noEmit --pretty --skipLibCheck</code>.
To make sure there aren&rsquo;t any type mismatches.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">lint:types</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_requests</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">except</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">$CI_COMMIT_REF_NAME =~ /^master/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn types-check</span><span class="w">
</span></span></span></code></pre></div><p>We run our unit tests using <code>jest</code> (our test runner). We also use the <code>--silent</code> flag to
hide various warnings raised by components we are testing. Like all the other jobs in this <code>stage</code> we
only run this job in an MR.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">tests:unit</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_requests</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">except</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">$CI_COMMIT_REF_NAME =~ /^master/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn tests --silent</span><span class="w">
</span></span></span></code></pre></div><p>Finally, almost the same as the job above, except it only runs on the <code>master</code> branch it gets the
code coverage from unit tests and stores the result using <code>coverage</code> (with some Regex). Where the <code>coverage</code> script is
defined as <code>jest --coverage</code> in <code>package.json</code>. <a href="https://docs.gitlab.com/ee/user/project/pipelines/settings.html#test-coverage-parsing">More information here</a>.
The code coverage can be shown on a badge, such as <a href="https://gitlab.com/hmajid2301/stegappasaurus/badges/master/coverage.svg">here</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">tests:coverage</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">master</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn coverage --silent</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">coverage</span><span class="p">:</span><span class="w"> </span><span class="l">/All\sfiles.*?\s+(\d+.\d+)/</span><span class="w">
</span></span></span></code></pre></div><h3 id="publish">publish</h3>
<p>Then on to our next stage. The job below actually publishes our app to the Play Store. It will only run when we&rsquo;ve
tagged one of our commits for release i.e. <code>release/1.0.0</code>. This will only be done on the <code>production</code> branch.
We are also using another docker image which has Android and various our dependencies need for our React Native app.</p>
<p>I won&rsquo;t do a massive deep dive into this job because I&rsquo;ve already written an article about it
<a href="https://dev.to/hmajid2301/auto-publish-react-native-app-to-android-play-store-using-gitlab-ci-44mc">here</a>.
But essentially what happens is we have various variables defined in our project in Gitlab such as our Keystore stored
in base64 and the Keystore setting such as the username and password.
To use the tool to auto-publish our app I need to have a <code>play-store.json</code> file and because my app uses react-native-firebase<code>I need a</code>google-services.json` file.</p>
<p>I then generate a <code>licenses.json</code> file using the following command <code>npm-license-crawler -onlyDirectDependencies --omitVersion -json src/data/licenses.json</code>,
there is a license view within my application which lists all of the main dependencies so I can properly credit those libraries this task generates that file.</p>
<p>I then generate a <code>gradle.propeties</code> file using <code>sh util/generate-gradle-properties.sh &gt; android/gradle.properties</code>.
Very similar to the <code>.env</code> the script we looked at above. Where the file looks something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env bash
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl">cat  <span class="s">&lt;&lt; EOF
</span></span></span><span class="line"><span class="cl"><span class="s">android.useAndroidX=true
</span></span></span><span class="line"><span class="cl"><span class="s">android.enableJetifier=true
</span></span></span><span class="line"><span class="cl"><span class="s">org.gradle.jvmargs=-Xms1g
</span></span></span><span class="line"><span class="cl"><span class="s">MYAPP_RELEASE_STORE_FILE=stegappasaurus.keystore
</span></span></span><span class="line"><span class="cl"><span class="s">MYAPP_RELEASE_STORE_PASSWORD=${ANDROID_KEYSTORE_PASSWORD}
</span></span></span><span class="line"><span class="cl"><span class="s">MYAPP_RELEASE_KEY_ALIAS=${ANDROID_KEYSTORE_ALIAS}
</span></span></span><span class="line"><span class="cl"><span class="s">MYAPP_RELEASE_KEY_PASSWORD=${ANDROID_KEYSTORE_KEY_PASSWORD}
</span></span></span><span class="line"><span class="cl"><span class="s">EOF</span>
</span></span></code></pre></div><p>This means we can reference the variables for the keystore within our <code>build.gradle</code> files without needing to hardcode
the values and once again this file is generated from CI variables stored on the project itself. For example the
<code>app/build.gradle</code> I have the following defined.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-groovy" data-lang="groovy"><span class="line"><span class="cl"><span class="n">android</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">signingConfigs</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">release</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="o">(</span><span class="n">project</span><span class="o">.</span><span class="na">hasProperty</span><span class="o">(</span><span class="s2">&#34;MYAPP_RELEASE_STORE_FILE&#34;</span><span class="o">))</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">storeFile</span> <span class="nf">file</span><span class="o">(</span><span class="n">MYAPP_RELEASE_STORE_FILE</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">                <span class="n">storePassword</span> <span class="n">MYAPP_RELEASE_STORE_PASSWORD</span>
</span></span><span class="line"><span class="cl">                <span class="n">keyAlias</span> <span class="n">MYAPP_RELEASE_KEY_ALIAS</span>
</span></span><span class="line"><span class="cl">                <span class="n">keyPassword</span> <span class="n">MYAPP_RELEASE_KEY_PASSWORD</span>
</span></span><span class="line"><span class="cl">            <span class="o">}</span>
</span></span><span class="line"><span class="cl">        <span class="o">}</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></div><p>We then publish the application using <code>publish-package</code> script which runs <code>yarn run bundle &amp;&amp; bash util/publish-package.sh</code>.
Where <code>publish-package.sh</code> looks like</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env bash
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$CI_COMMIT_TAG</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[[</span> <span class="nv">$CI_COMMIT_TAG</span> <span class="o">==</span> *<span class="s2">&#34;alpha&#34;</span>* <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;Publishing Package: Alpha&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nb">cd</span> android <span class="o">&amp;&amp;</span> ./gradlew publish --track alpha
</span></span><span class="line"><span class="cl"><span class="k">elif</span> <span class="o">[[</span> <span class="nv">$CI_COMMIT_TAG</span> <span class="o">==</span> *<span class="s2">&#34;beta&#34;</span>* <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;Publishing Package: Beta&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nb">cd</span> android <span class="o">&amp;&amp;</span> ./gradlew publish --track beta
</span></span><span class="line"><span class="cl"><span class="k">elif</span> <span class="o">[[</span> <span class="nv">$CI_COMMIT_TAG</span> <span class="o">==</span> *<span class="s2">&#34;release&#34;</span>* <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;Publishing Package: Production&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nb">cd</span> android <span class="o">&amp;&amp;</span> ./gradlew publish --track production
</span></span><span class="line"><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;Publishing Package: Internal&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nb">cd</span> android <span class="o">&amp;&amp;</span> ./gradlew publish --track internal
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span></code></pre></div><p>If the git tag is <code>release/1.0.0</code> then we will publish this directly onto the production track. It also
check if the tag contains <code>alpha</code> or <code>beta</code> if so then we publish it to different tracks.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Publishing Package: Production&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> android <span class="o">&amp;&amp;</span> ./gradlew publish --track production
</span></span></code></pre></div><p>Finally we make the <code>assets</code> and <code>build</code> folders available as
<a href="https://docs.gitlab.com/ee/user/project/pipelines/job_artifacts.html">artifacts</a> for jobs in future stages.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">publish:android:package</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">reactnativecommunity/react-native-android</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">/^release/.*$/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">echo fs.inotify.max_user_watches=524288 | tee -a /etc/sysctl.conf &amp;&amp; sysctl -p</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">cd android</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">base64 -d $ANDROID_KEYSTORE &gt; app/stegappasaurus.keystore</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">export VERSION=$(cat app.json | jq -r .version)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">cat $PLAY_STORE_JSON &gt; app/play-store.json</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">cat $FIREBASE_GOOGLE_SERVICES_JSON &gt; app/google-services.json</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn generate-licenses</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn generate-gradle-properties</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn publish-package --no-daemon</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">artifacts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./android/app/build/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./android/app/src/main/assets/</span><span class="w">
</span></span></span></code></pre></div><h3 id="post">post</h3>
<p>Onto our final stage, the first job creates a Gitlab release. This job is again only run on release tags, but only
for &ldquo;final&rdquo; release hence the <code>except</code> clause. It won&rsquo;t run if the git tag contains <code>beta</code> or <code>alpha</code> in its name.
The <a href="https://gitlab.com/gitlab-automation-toolkit/gitlab-auto-release">gitlab-auto-release</a> tool much like the other
tools above is used to automate this part of the Gitlab workflow.</p>
<p>The script also can use <code>CHANGELOG.md</code>, if it follows
<a href="https://gitlab.com/gitlab-automation-toolkit/gitlab-auto-release">keepachangelog</a> format. It takes the changelog from that
file and copies into our release. Only for the matching version name i.e. <code>release/1.0.0</code>, would look for <code>1.0.0</code> in our changelog file.
You can find an example release created by this script <a href="https://gitlab.com/hmajid2301/stegappasaurus/-/tags/release%2F1.0.1">here</a>.
Also if you specify a job name after the <code>--artifacts</code> argument it will link that jobs artifacts in this release (if it was run in the
same pipeline as this job). In this example, we want to include our Android app build (APK/AAB).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">create:gitlab:release</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">registry.gitlab.com/gitlab-automation-toolkit/gitlab-auto-release</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">post</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">/^release/.*$/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">except</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">$CI_COMMIT_TAG =~ /beta/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">$CI_COMMIT_TAG =~ /alpha/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">before_script</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">gitlab_auto_release -c CHANGELOG.md -d &#34;This was auto-generated by the gitlab-auto-release tool, https://gitlab.com/gitlab-automation-toolkit/gitlab-auto-release.&#34; --artifacts &#34;publish:android:package&#34;</span><span class="w">
</span></span></span></code></pre></div><p>Our final job in this stage again only runs on release tags. It publishes our source maps to <a href="https://www.bugsnag.com/">Bugsnag</a>.
Which is a bug tracking tool. When our app is published to the Play store the JavaScript is minified and so Bugsnag cannot
give us a proper stack trace without the source maps. We must &ldquo;tag&rdquo; each upload with a version, hence we look in <code>app.json</code> file
for the current app version. This job requires artifacts from the previous android publishing job <code>publish:android:package</code>,
hence we mark it a dependency in <code>dependencies</code>. We need access to the bundle generated in the <code>assets</code> folder from the
previous job. Rather than repeat the same &ldquo;actions&rdquo; here to generate the files we need.
To speed up our CI we will just &ldquo;copy&rdquo; the files into the job by using artifacts.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">publish:bugsnag:soucemaps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">post</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">/^release/.*$/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">apt update &amp;&amp; apt install -y jq</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">export VERSION=$(cat app.json | jq -r .version)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">curl https://upload.bugsnag.com/react-native-source-map -F apiKey=${BUGSNAG_API_KEY} -F appVersion=${VERSION} -F dev=false -F platform=android -F sourceMap=@android/app/src/main/assets/index.map -F bundle=@android/app/src/main/assets/index.bundle -F projectRoot=`pwd`</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn run bugsnag-sourcemaps upload --api-key=${BUGSNAG_API_KEY} --app-version=${VERSION} --minifiedFile=android/app/build/generated/assets/react/release/index.android.bundle --source-map=android/app/build/generated/sourcemaps/react/release/index.android.bundle.map --minified-url=index.android.bundle --upload-sources</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">dependencies</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">publish:android:package</span><span class="w">
</span></span></span></code></pre></div><h3 id="other">other</h3>
<p>Finally, we have a Gitlab defined job called pages, where using Gitlab pages we will publish documentation
for this application. It will publish a static website present in the <code>public</code>. The documentation is built
using <a href="docz.site">docz</a>. By default, you can access pages at <a href="https://hmajid2301.gitlab.io/stegappasaurus">https://hmajid2301.gitlab.io/stegappasaurus</a>,
i.e. username.gitlab.io/project_name but I have a google domain and using a <code>CNAME</code> you can also view the
website on <code>https://stegappasaurus.haseebmajid.dev/</code>.</p>
<p>Since this is a special job this job is run at the very end of our pipeline, also this job on runs on the
<code>master</code> branch.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">pages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">master</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">before_script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn docs-build</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">mv .docz/dist/* public/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">artifacts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">public</span><span class="w">
</span></span></span></code></pre></div><p>Finally it&rsquo;s done! That&rsquo;s it! That is one example of a <code>.gitlab-ci.yml</code> file you can use to for
your React Native projects.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/stegappasaurus">Example Project</a></li>
<li><a href="https://about.gitlab.com/images/ci/ci-cd-test-deploy-illustration_2x.png">Cover image</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Using React Native with Firebase Cloud Functions and Gitlab CI</title>
      <link>https://haseebmajid.dev/posts/2020-02-22-using-react-native-with-firebase-cloud-functions-and-gitlab-ci/</link>
      <pubDate>Sat, 22 Feb 2020 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2020-02-22-using-react-native-with-firebase-cloud-functions-and-gitlab-ci/</guid>
      <description>&lt;p&gt;In this article, we will talk about how you can use React Native with &lt;a href=&#34;https://firebase.google.com/docs/functions&#34;&gt;Firebase Cloud Functions&lt;/a&gt;.
We will also go over how we can automate the process of updating the cloud functions using &lt;a href=&#34;https://docs.gitlab.com/ee/ci/&#34;&gt;Gitlab CI&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://firebase.google.com/&#34;&gt;Firebase&lt;/a&gt; is a cloud-based platform developed by Google to aid in the development of Web and Mobile applications. It is tightly
coupled with the &lt;a href=&#34;https://cloud.google.com/&#34;&gt;Google Cloud Platform (GCP)&lt;/a&gt;, so much so that there are certain actions you can
only do using the GCP GUI, such as increasing the RAM of your cloud function &amp;ldquo;containers&amp;rdquo;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this article, we will talk about how you can use React Native with <a href="https://firebase.google.com/docs/functions">Firebase Cloud Functions</a>.
We will also go over how we can automate the process of updating the cloud functions using <a href="https://docs.gitlab.com/ee/ci/">Gitlab CI</a>.</p>
<p><a href="https://firebase.google.com/">Firebase</a> is a cloud-based platform developed by Google to aid in the development of Web and Mobile applications. It is tightly
coupled with the <a href="https://cloud.google.com/">Google Cloud Platform (GCP)</a>, so much so that there are certain actions you can
only do using the GCP GUI, such as increasing the RAM of your cloud function &ldquo;containers&rdquo;.</p>
<p><strong>Note</strong>: We will be using Typescript in this article</p>
<h2 id="firebase-cloud-functions">Firebase Cloud Functions</h2>
<p>Firebase Cloud Functions can be referred to as serverless or as Functions-as-a-service (FaaS).
This means we simply deploy our code as a function, the tool (Firebase) installs our dependencies
and set up the environment. Essentially all we manage is the &ldquo;code&rdquo; and let the platform manage the
actual server/environment.</p>
<p><strong>Note</strong>: You can deploy cloud functions in Python and Golang, however, you must do this through the
GCP GUI. The functions will show up on your Firebase GUI after you&rsquo;ve created them.
You can view your Firebase project within GCP hence you can make changes to it such
as increasing the RAM (from 512MB to 2GB) from within GCP.</p>
<p>Let&rsquo;s now take a look at a simple app we will deploy to Firebase Cloud Functions.</p>
<h3 id="structure">Structure</h3>
<p>Our project structure will look something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">├── firebase.json
</span></span><span class="line"><span class="cl">├── .firebaserc
</span></span><span class="line"><span class="cl">├── functions
</span></span><span class="line"><span class="cl">│   ├── index.ts
</span></span><span class="line"><span class="cl">│   ├── middleware
</span></span><span class="line"><span class="cl">│   ├── node_modules
</span></span><span class="line"><span class="cl">│   ├── package.json
</span></span><span class="line"><span class="cl">│   ├── tsconfig.json
</span></span><span class="line"><span class="cl">│   └── yarn.lock
</span></span><span class="line"><span class="cl">├── .gitignore
</span></span><span class="line"><span class="cl">└── .gitlab-ci.yml
</span></span></code></pre></div><p>This setup will look very similar to the tutorial <a href="https://firebase.google.com/docs/functions/get-started">available here</a>.</p>
<p><img
        loading="lazy"
        src="/posts/2020-02-22-using-react-native-with-firebase-cloud-functions-and-gitlab-ci/images/firebase.gif"
        type=""
        alt="Firebase Pizza"
        
      /></p>
<h3 id="firebase-misc-files">Firebase Misc Files</h3>
<p>This file contains some configuration options but for most projects, it will just contain the project name
(the one we want to publish our changes to on Firebase, as we could be working on multiple projects).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;projects&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;default&#34;</span><span class="p">:</span> <span class="s2">&#34;ExampleProject&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This file is important as it defines the actions that will happen before we deploy a new version
of the cloud functions. In this case, we run <code>yarn run build</code>, within the <code>functions</code> folder.
It compiles our TypeScript (TS) into regular JavaScript (JS) so that it can be run
as a cloud function. You could do various other actions such as lint your code etc.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;functions&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;predeploy&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;yarn --cwd \&#34;$RESOURCE_DIR\&#34; run build&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="gitlab-ci">Gitlab CI</h3>
<p>Now you&rsquo;re probably wondering how do we get our Cloud Functions from our dev machine (computer) to the Firebase servers.
We run the <code>deploy</code> script command. Now we could do this every time we make a change, however, I prefer to automate this process.</p>
<p>We will use Gitlab CI to automatically publish changes to Firebase.
First, we will need a deploy token as we cannot enter our username and password within GitLab
CI to do this run <code>yarn firebase login:ci</code>. Then log in to your Firebase account after you&rsquo;ve done this you will get a deploy token (shown in the terminal), then;</p>
<ul>
<li>Open your Gitlab project in a web browser</li>
<li>Go to Settings (left-hand sidebar) &gt; CI/CD</li>
<li>Variables -&gt; Expand</li>
<li>Add a new variable, with Type: Variable, Key: FIREBASE_DEPLOY_TOKEN, Value: <code>your deploy token here</code>, and toggle protected and masked as true (blue).</li>
</ul>
<p>This now means you can access the token within the Gitlab CI as an environment variable,
and it will allow us to authenticate with Firebase and push changes to Firebase.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">node:8</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">publish:firebase:functions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">/^release/.*$/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">cd functions</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">yarn run deploy -m &#34;Pipeline $CI_PIPELINE_ID, build $CI_BUILD_ID&#34; --non-interactive --token $FIREBASE_DEPLOY_TOKEN</span><span class="w">
</span></span></span></code></pre></div><p>The CI file we&rsquo;ve defined means every time we commit onto the master branch it will trigger
a deployment of our code to Firebase Cloud Functions. We add a message so we know which
pipeline triggered the build <code>-m</code>. Gitlab provides some predefined<br>
<a href="https://docs.gitlab.com/ee/ci/variables/predefined_variables.html">environment variables</a>.
Two of those being the ones within our message.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">yarn run deploy -m <span class="s2">&#34;Pipeline </span><span class="nv">$CI_PIPELINE_ID</span><span class="s2">, build </span><span class="nv">$CI_BUILD_ID</span><span class="s2">&#34;</span> --non-interactive --token <span class="nv">$FIREBASE_DEPLOY_TOKEN</span>
</span></span></code></pre></div><p>When we trigger the deploy script it will look within our <code>firebase.json</code> file and then
run the <code>predeploy</code> commands, which will transpile our code from TS -&gt; JS.</p>
<h3 id="functions">Functions</h3>
<p>This folder contains our (Express) web service, i.e. it has our actual code.</p>
<p>The <code>package.json</code> file is used to install all of our dependencies inside the serverless environment.
It also defines the <code>build</code> script that will be used in the pre-deploy process before the code
is deployed to Firebase.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="nt">&#34;main&#34;</span><span class="p">:</span> <span class="s2">&#34;lib/index.js&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;scripts&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;build&#34;</span><span class="p">:</span> <span class="s2">&#34;tsc -p . --skipLibCheck&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;deploy&#34;</span><span class="p">:</span> <span class="s2">&#34;firebase deploy --only functions&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="nt">&#34;engines&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;node&#34;</span><span class="p">:</span> <span class="s2">&#34;8&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>When we run the <code>build</code> script we create a <code>lib</code> folder which contains the compiled (JS). Hence the main file
is <code>lib/index.js</code>. The lib folder is created because we specify the <code>outDir</code> to be <code>lib</code> in the <code>tsconfig.json</code>.
The Firebase Cloud Functions by default uses NodeJS (as stated above this can be changed in the GCP GUI) to run
our Firebase Cloud Functions, hence our code needs to be compiled to JS from TS before we deploy it.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;compilerOptions&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="nt">&#34;outDir&#34;</span><span class="p">:</span> <span class="s2">&#34;lib&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Now let&rsquo;s take a look at the &ldquo;business&rdquo; logic of the application.</p>
<p>This file contains all the core logic for our web service. Here we define
two endpoints called <code>hello</code> and <code>bye</code>. As stated earlier this will be the entry point
into our application. This is the file that will set up and start are Express server/web service within the
Firebase Cloud environment.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">express</span> <span class="kr">from</span> <span class="s2">&#34;express&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">initializeApp</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;firebase-admin&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">https</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;firebase-functions&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">ValidateToken</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;./middleware&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">initializeApp</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">express</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">express</span><span class="p">.</span><span class="nx">json</span><span class="p">());</span>
</span></span><span class="line"><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">ValidateToken</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="s2">&#34;/hello&#34;</span><span class="p">,</span> <span class="nx">hello</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="s2">&#34;/bye&#34;</span><span class="p">,</span> <span class="nx">bye</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">hello</span><span class="p">(</span><span class="nx">request</span>: <span class="kt">express.Request</span><span class="p">,</span> <span class="nx">response</span>: <span class="kt">express.Response</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">name</span> <span class="o">=</span> <span class="nx">request</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">name</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">response</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">200</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span> <span class="nx">hello</span><span class="o">:</span> <span class="sb">`Hello </span><span class="si">${</span><span class="nx">name</span><span class="si">}</span><span class="sb">`</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">bye</span><span class="p">(</span><span class="nx">request</span>: <span class="kt">express.Request</span><span class="p">,</span> <span class="nx">response</span>: <span class="kt">express.Response</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">name</span> <span class="o">=</span> <span class="nx">request</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">name</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">response</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">200</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span> <span class="nx">bye</span><span class="o">:</span> <span class="sb">`Bye </span><span class="si">${</span><span class="nx">name</span><span class="si">}</span><span class="sb">`</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="nx">api</span> <span class="o">=</span> <span class="nx">https</span><span class="p">.</span><span class="nx">onRequest</span><span class="p">(</span><span class="nx">app</span><span class="p">);</span>
</span></span></code></pre></div><p>Breaking down the file first, we set up our web service. We tell it to use the JSON
middleware alongside our custom <code>ValidateToken</code>. These will run before the request is passed
to our two endpoints helping to reduce boilerplate code, as common functionality between
endpoints can be split out into middleware functions.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">initializeApp</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">express</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">express</span><span class="p">.</span><span class="nx">json</span><span class="p">());</span>
</span></span><span class="line"><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">ValidateToken</span><span class="p">);</span>
</span></span></code></pre></div><p>Then we define our endpoints in this case two very simple endpoints <code>/hello</code> and <code>/bye</code>,
that receive a field called <code>name</code> in the request body, we return a <code>200</code> status
code alongside a message (returned as JSON).</p>
<p>We split out <code>hello</code> and <code>bye</code> into separate functions as it&rsquo;s a bit easier to read,
we could also split this out into separate files if the logic gets more complicated,
but in this example, it&rsquo;s simple enough to leave it all in this single file.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="s2">&#34;/hello&#34;</span><span class="p">,</span> <span class="nx">hello</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="s2">&#34;/bye&#34;</span><span class="p">,</span> <span class="nx">bye</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">hello</span><span class="p">(</span><span class="nx">request</span>: <span class="kt">express.Request</span><span class="p">,</span> <span class="nx">response</span>: <span class="kt">express.Response</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">body</span> <span class="o">=</span> <span class="nx">request</span><span class="p">.</span><span class="nx">body</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">name</span> <span class="o">=</span> <span class="nx">body</span><span class="p">.</span><span class="nx">name</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">response</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">200</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span> <span class="nx">hello</span><span class="o">:</span> <span class="sb">`Hello </span><span class="si">${</span><span class="nx">name</span><span class="si">}</span><span class="sb">`</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">bye</span><span class="p">(</span><span class="nx">request</span>: <span class="kt">express.Request</span><span class="p">,</span> <span class="nx">response</span>: <span class="kt">express.Response</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">body</span> <span class="o">=</span> <span class="nx">request</span><span class="p">.</span><span class="nx">body</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">name</span> <span class="o">=</span> <span class="nx">body</span><span class="p">.</span><span class="nx">name</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">response</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">200</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span> <span class="nx">bye</span><span class="o">:</span> <span class="sb">`Bye </span><span class="si">${</span><span class="nx">name</span><span class="si">}</span><span class="sb">`</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="nx">api</span> <span class="o">=</span> <span class="nx">https</span><span class="p">.</span><span class="nx">onRequest</span><span class="p">(</span><span class="nx">app</span><span class="p">);</span>
</span></span></code></pre></div><h4 id="middleware-optional">middleware (optional)</h4>
<p>The middleware folder stores all of our server middleware, these are functions that are usually called before
every request. Hence we don&rsquo;t have to explicilty call them on all of our endpoints. <code>Express</code> handles this for
us and automatically runs the middleware before the endpoint function is called.</p>
<p>We are checking the <code>Authorization</code> token sent with the request is validate, by default
our Firebase Cloud Function endpoints are accessible by anyone. We can restrict
who has access to them by requiring the client to send a token. As you can see below we do this using Firebase&rsquo;s
own auth component.</p>
<p><strong>Note</strong>: Don&rsquo;t worry, your users don&rsquo;t need to sign up for you to &ldquo;authenticate/authorisation&rdquo; them.</p>
<h5 id="validatetokents">ValidateToken.ts</h5>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="o">*</span> <span class="kr">as</span> <span class="nx">express</span> <span class="kr">from</span> <span class="s2">&#34;express&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">auth</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;firebase-admin&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">ValidateToken</span> <span class="o">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="nx">request</span>: <span class="kt">express.Request</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">response</span>: <span class="kt">express.Response</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">next</span>: <span class="kt">express.NextFunction</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kd">let</span> <span class="nx">token</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nx">request</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nx">authorization</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">request</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nx">authorization</span><span class="p">.</span><span class="nx">startsWith</span><span class="p">(</span><span class="s2">&#34;Bearer &#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">token</span> <span class="o">=</span> <span class="nx">request</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nx">authorization</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="s2">&#34;Bearer &#34;</span><span class="p">)[</span><span class="mi">1</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">response</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">403</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span> <span class="nx">code</span><span class="o">:</span> <span class="s2">&#34;unauthorized&#34;</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">auth</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nx">verifyIdToken</span><span class="p">(</span><span class="nx">token</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nx">then</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="k">return</span> <span class="nx">next</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="k">catch</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">response</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">403</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span> <span class="nx">code</span><span class="o">:</span> <span class="s2">&#34;unauthorized&#34;</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">ValidateToken</span><span class="p">;</span>
</span></span></code></pre></div><p>Breaking down the file, first we check if the request header contains the <code>Authorization</code> parameter
and that parameter has a form similar to
<a href="https://swagger.io/docs/specification/authentication/bearer-authentication/"><code>Bearer $TOKEN</code></a>.
If not, we return a <code>403</code> <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status">HTTP error</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="nx">request</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nx">authorization</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">request</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nx">authorization</span><span class="p">.</span><span class="nx">startsWith</span><span class="p">(</span><span class="s2">&#34;Bearer &#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">token</span> <span class="o">=</span> <span class="nx">request</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nx">authorization</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="s2">&#34;Bearer &#34;</span><span class="p">)[</span><span class="mi">1</span><span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">response</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">403</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span> <span class="nx">code</span><span class="o">:</span> <span class="s2">&#34;unauthorized&#34;</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Then we use Firebase admin to verify if the token is valid. If so, we pass the request on with the <code>next()</code> function.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="nx">auth</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">  <span class="p">.</span><span class="nx">verifyIdToken</span><span class="p">(</span><span class="nx">token</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">.</span><span class="nx">then</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">next</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl">  <span class="p">.</span><span class="k">catch</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">response</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">403</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span> <span class="nx">code</span><span class="o">:</span> <span class="s2">&#34;unauthorized&#34;</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span></code></pre></div><p>Finally we have an <code>index.ts</code> to make for cleaner import/export.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">ValidateToken</span> <span class="kr">from</span> <span class="s2">&#34;./ValidateToken&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="p">{</span> <span class="nx">ValidateToken</span> <span class="p">};</span>
</span></span></code></pre></div><h2 id="react-native">React Native</h2>
<p>Next let&rsquo;s take a look at our React Native logic and how we interact with the Firebase Cloud Functions.
I created a new app using the following command:</p>
<p><code>react-native init MyAwesomeProject --template typescript</code></p>
<h3 id="structure-1">Structure</h3>
<p>This is the project structure of our React Native app:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">.
</span></span><span class="line"><span class="cl">└── ExampleApp
</span></span><span class="line"><span class="cl">    ├── android
</span></span><span class="line"><span class="cl">    ├── app.json
</span></span><span class="line"><span class="cl">    ├── App.tsx
</span></span><span class="line"><span class="cl">    ├── babel.config.js
</span></span><span class="line"><span class="cl">    ├── .buckconfig
</span></span><span class="line"><span class="cl">    ├── .eslintrc.js
</span></span><span class="line"><span class="cl">    ├── .flowconfig
</span></span><span class="line"><span class="cl">    ├── .gitattributes
</span></span><span class="line"><span class="cl">    ├── .gitignore
</span></span><span class="line"><span class="cl">    ├── index.js
</span></span><span class="line"><span class="cl">    ├── ios
</span></span><span class="line"><span class="cl">    ├── LICENSE
</span></span><span class="line"><span class="cl">    ├── metro.config.js
</span></span><span class="line"><span class="cl">    ├── node_modules
</span></span><span class="line"><span class="cl">    ├── package.json
</span></span><span class="line"><span class="cl">    ├── .prettierrc.js
</span></span><span class="line"><span class="cl">    ├── README.md
</span></span><span class="line"><span class="cl">    ├── template.config.js
</span></span><span class="line"><span class="cl">    ├── __tests__
</span></span><span class="line"><span class="cl">    ├── tsconfig.json
</span></span><span class="line"><span class="cl">    ├── .watchmanconfig
</span></span><span class="line"><span class="cl">    └── yarn.lock
</span></span></code></pre></div><p>This file contains most of our logic:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span><span class="nx">ApiResponse</span><span class="p">,</span> <span class="nx">create</span><span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;apisauce&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span> <span class="kr">from</span> <span class="s1">&#39;react&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span><span class="nx">Button</span><span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;react-native&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span><span class="nx">firebase</span><span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;@react-native-firebase/auth&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">App</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">Button</span> <span class="na">title</span><span class="o">=</span><span class="s">&#34;Make Request&#34;</span> <span class="na">onPress</span><span class="o">=</span><span class="p">{()</span> <span class="o">=&gt;</span> <span class="nx">makeRequest</span><span class="p">()}&gt;&lt;/</span><span class="nt">Button</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">async</span> <span class="kd">function</span> <span class="nx">makeRequest() {</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">userCredentials</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">firebase</span><span class="p">.</span><span class="nx">auth</span><span class="p">().</span><span class="nx">signInAnonymously</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">token</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">userCredentials</span><span class="p">.</span><span class="nx">user</span><span class="p">.</span><span class="nx">getIdToken</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">api</span> <span class="o">=</span> <span class="nx">create</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nx">baseURL</span><span class="o">:</span> <span class="s1">&#39;https://us-central1-exampleapp.cloudfunctions.net&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">headers</span><span class="o">:</span> <span class="p">{</span><span class="nx">Authorization</span><span class="o">:</span> <span class="sb">`Bearer </span><span class="si">${</span><span class="nx">token</span><span class="si">}</span><span class="sb">`</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nx">timeout</span>: <span class="kt">10000</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">response</span>: <span class="kt">ApiResponse</span><span class="o">&lt;</span><span class="p">{</span><span class="nx">hello</span>: <span class="kt">string</span><span class="p">}</span><span class="o">&gt;</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">api</span><span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="s1">&#39;/hello&#39;</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">name</span><span class="o">:</span> <span class="s1">&#39;Haseeb&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="p">{</span><span class="nx">data</span><span class="p">,</span> <span class="nx">ok</span><span class="p">,</span> <span class="nx">status</span><span class="p">}</span> <span class="o">=</span> <span class="nx">response</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">ok</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;Success&#39;</span><span class="p">,</span> <span class="nx">status</span><span class="p">,</span> <span class="nx">data</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s1">&#39;error&#39;</span><span class="p">,</span> <span class="nx">status</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s1">&#39;Error thrown&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">App</span><span class="p">;</span>
</span></span></code></pre></div><p>The main page will have a single button which when pressed will make a request to our Firebase Cloud Functions.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">App</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">Button</span> <span class="na">title</span><span class="o">=</span><span class="s">&#34;Make Request&#34;</span> <span class="na">onPress</span><span class="o">=</span><span class="p">{()</span> <span class="o">=&gt;</span> <span class="nx">makeRequest</span><span class="p">()}&gt;&lt;/</span><span class="nt">Button</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span></code></pre></div><p>Then in the <code>makeRequest()</code> function we use
<a href="https://invertase.io/oss/react-native-firebase/">react-native-firebase</a> for the authentication (optional)
if you set up the authentication middleware in the firebase functions. You can use the
<a href="https://invertase.io/oss/react-native-firebase/quick-start/existing-project">following tutorial</a> to get
started with the library.
The following allows any user of our app to get a token we can send with our HTTP request.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">userCredentials</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">firebase</span><span class="p">.</span><span class="nx">auth</span><span class="p">().</span><span class="nx">signInAnonymously</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">token</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">userCredentials</span><span class="p">.</span><span class="nx">user</span><span class="p">.</span><span class="nx">getIdToken</span><span class="p">();</span>
</span></span></code></pre></div><p>We use <code>apisauce</code> to make HTTP requests, but first we must &ldquo;create&rdquo; an API object. Here is where we pass our auth token.</p>
<p><strong>NOTE</strong>: Remember to replace <code>baseURL</code> with your URL.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">api</span> <span class="o">=</span> <span class="nx">create</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nx">baseURL</span><span class="o">:</span> <span class="s2">&#34;https://us-central1-exampleapp.cloudfunctions.net&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">headers</span><span class="o">:</span> <span class="p">{</span> <span class="nx">Authorization</span><span class="o">:</span> <span class="sb">`Bearer </span><span class="si">${</span><span class="nx">token</span><span class="si">}</span><span class="sb">`</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nx">timeout</span>: <span class="kt">10000</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><p>Then we specify the <code>/hello</code> endpoint. The response contains a few parameters, if <code>ok</code> is set to <code>true</code>
then the request was successful (<code>2xx</code> HTTP code).</p>
<p>We then log the response from the server. In reality you will want to do something more
useful than that but this is just a simple example.
All of this code is all surrounded by a try catch so if a reject promise is returned, it will be
captured by the <code>catch</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">response</span>: <span class="kt">ApiResponse</span><span class="o">&lt;</span><span class="p">{</span> <span class="nx">hello</span>: <span class="kt">string</span> <span class="p">}</span><span class="o">&gt;</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">api</span><span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="s2">&#34;/hello&#34;</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">name</span><span class="o">:</span> <span class="s2">&#34;Haseeb&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="p">{</span> <span class="nx">data</span><span class="p">,</span> <span class="nx">ok</span><span class="p">,</span> <span class="nx">status</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">response</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="nx">ok</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">&#34;Success&#34;</span><span class="p">,</span> <span class="nx">status</span><span class="p">,</span> <span class="nx">data</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s2">&#34;error&#34;</span><span class="p">,</span> <span class="nx">status</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p><strong>Note</strong>: Sometimes your Cloud Functions may run a bit slower the first time you call them (in a while).
You need to keep your functions &ldquo;warm&rdquo; as they say, as long as you&rsquo;re running the functions the container
they are running in stays alive, after a period of time it is destroyed and needs to be recreated, hence
after a long period of time since the function was called it may well be a few seconds slower.</p>
<p>That&rsquo;s it! Ee succesfully set up a React Native application to use Cloud Functions we deployed on
Firebase (with authentication).</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2020-02-22-using-react-native-with-firebase-cloud-functions-and-gitlab-ci/source_code">Example source code</a></li>
<li><a href="https://gitlab.com/hmajid2301/stegappasaurus-api">Example Firebase project</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Auto Toggle Dark Theme on your React Native App</title>
      <link>https://haseebmajid.dev/posts/2020-01-25-auto-toggle-dark-theme-on-your-react-native-app/</link>
      <pubDate>Sat, 25 Jan 2020 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2020-01-25-auto-toggle-dark-theme-on-your-react-native-app/</guid>
      <description>&lt;p&gt;In this article, I will show you how you can change the theme of your app depending on
the time of the day. We will change the theme of the app depending on if the sun has set or risen.&lt;/p&gt;
&lt;h2 id=&#34;our-application&#34;&gt;Our Application&lt;/h2&gt;
&lt;p&gt;To get started we will create a new React Native app by running the following command,
&lt;code&gt;react-native init ExampleApp --template typescript&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note&lt;/em&gt;: We are using path aliases so &lt;code&gt;~&lt;/code&gt; is the same as saying &lt;code&gt;src/&lt;/code&gt;, this keeps the
import paths cleaner. More information &lt;a href=&#34;https://haseebmajid.dev/posts/2019-12-01-better-imports-with-typescript-aliases-babel-and-tspath/&#34;&gt;here&lt;/a&gt; #ShamelessPlug.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this article, I will show you how you can change the theme of your app depending on
the time of the day. We will change the theme of the app depending on if the sun has set or risen.</p>
<h2 id="our-application">Our Application</h2>
<p>To get started we will create a new React Native app by running the following command,
<code>react-native init ExampleApp --template typescript</code>.</p>
<p><em>Note</em>: We are using path aliases so <code>~</code> is the same as saying <code>src/</code>, this keeps the
import paths cleaner. More information <a href="/posts/2019-12-01-better-imports-with-typescript-aliases-babel-and-tspath/">here</a> #ShamelessPlug.</p>
<h3 id="auto-theme">Auto Theme</h3>
<p>First, let&rsquo;s create the module which will contain the core logic for this app.
This module will be used to determine if we should toggle the dark theme on or off.
It does this by using the user&rsquo;s current location, using the
<a href="https://github.com/timfpark/react-native-location">react-native-location</a> library.
Then working out the sunrise and sunset at that location, using <a href="https://github.com/udivankin/sunrise-sunset">sunrise-sunset-js</a>.</p>
<p>However, we will only check the location once per day, we store the latitude and longitude locally
on the device and if it within a day since it was set, then we use these locally stored values.
However if the stored values are older than a day then we find the new latitude and longitude,
use those and replace the old values with these new values.</p>
<p>The AutoTheme is a class, let&rsquo;s take a look at the main function of the class</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="kr">class</span> <span class="nx">AutoTheme</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">private</span> <span class="kr">static</span> <span class="nx">oneDay</span> <span class="o">=</span> <span class="mi">24</span> <span class="o">*</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">60</span> <span class="o">*</span> <span class="mi">1000</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">public</span> <span class="kr">async</span> <span class="nx">shouldToggleDarkTheme() {</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">currentTime</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="nb">Date</span><span class="p">.</span><span class="nx">now</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="p">{</span> <span class="nx">sunrise</span><span class="p">,</span> <span class="nx">sunset</span> <span class="p">}</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">getSunriseAndSunsetTime</span><span class="p">(</span><span class="nx">currentTime</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kd">let</span> <span class="nx">toggleTheme</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">sunrise</span> <span class="o">!==</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="nx">sunset</span> <span class="o">!==</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="k">if</span> <span class="p">(</span><span class="nx">currentTime</span> <span class="o">&gt;</span> <span class="nx">sunrise</span> <span class="o">&amp;&amp;</span> <span class="nx">currentTime</span> <span class="o">&lt;</span> <span class="nx">sunset</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">toggleTheme</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">toggleTheme</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span></code></pre></div><p>The logic is fairly simple</p>
<ul>
<li>Get the current time</li>
<li>Get the time the sun will rise/set</li>
<li>If the current time is between sunrise and sunset
<ul>
<li>then leave the theme light (return false, i.e. don&rsquo;t toggle the theme to dark)</li>
</ul>
</li>
<li>else
<ul>
<li>toggle the theme to dark</li>
</ul>
</li>
</ul>
<p>In the example below, we would toggle the dark theme on because the sun has already set for that day.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">currentTime = 5.48pm
</span></span><span class="line"><span class="cl">sunrise = 6.30am
</span></span><span class="line"><span class="cl">sunset = 4.45pm
</span></span></code></pre></div><p>So how do we get the sunrise/sunset time ? First, we need to get the latitude and longitude.
Then using the latitude and longitude we work out the sunset and sunrise times (for the current day).
Sometimes with the sunset-sunrise library, it will show you the sunrise for the next day.
If this is the case we simply remove a day from the sunrise date, so we&rsquo;re always
comparing the sunrise/sunset and current time on the same day.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl">  <span class="kr">private</span> <span class="kr">async</span> <span class="nx">getSunriseAndSunsetTime</span><span class="p">(</span><span class="nx">currentTime</span>: <span class="kt">Date</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="p">{</span><span class="nx">latitude</span><span class="p">,</span> <span class="nx">longitude</span><span class="p">}</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">getLatitudeLongitude</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="kd">let</span> <span class="nx">sunrise</span> <span class="o">=</span> <span class="nx">getSunrise</span><span class="p">(</span><span class="nx">latitude</span><span class="p">,</span> <span class="nx">longitude</span><span class="p">,</span> <span class="nx">currentTime</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">sunset</span> <span class="o">=</span> <span class="nx">getSunset</span><span class="p">(</span><span class="nx">latitude</span><span class="p">,</span> <span class="nx">longitude</span><span class="p">,</span> <span class="nx">currentTime</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">sunrise</span> <span class="o">&gt;</span> <span class="nx">sunset</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">sunrise</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="nx">sunset</span><span class="p">.</span><span class="nx">getTime</span><span class="p">()</span> <span class="o">-</span> <span class="nx">AutoTheme</span><span class="p">.</span><span class="nx">oneDay</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">{</span><span class="nx">sunset</span><span class="p">,</span> <span class="nx">sunrise</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span></code></pre></div><p>As stated above we get the latitude-longitude data either from local storage (async storage),
or we get completely new latitude-longitude data from the users&rsquo;s current location.
We check if the stored location is older than a day and if it is we get the user&rsquo;s current location.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl">  <span class="kr">private</span> <span class="kr">async</span> <span class="nx">getLatitudeLongitude() {</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">currentDate</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="nb">Date</span><span class="p">.</span><span class="nx">now</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">lastQueried</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">AsyncStorage</span><span class="p">.</span><span class="nx">getItem</span><span class="p">(</span><span class="s1">&#39;@LastQueriedLocation&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kd">let</span> <span class="nx">latitude</span>: <span class="kt">number</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">let</span> <span class="nx">longitude</span>: <span class="kt">number</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">let</span> <span class="nx">lastQueriedDate</span>: <span class="kt">Date</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">lastQueried</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">lastQueriedDate</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="nx">lastQueried</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">lastQueriedDate</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(</span><span class="nx">currentDate</span><span class="p">.</span><span class="nx">getTime</span><span class="p">()</span> <span class="o">-</span> <span class="nx">AutoTheme</span><span class="p">.</span><span class="nx">oneDay</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">currentDate</span><span class="p">.</span><span class="nx">getTime</span><span class="p">()</span> <span class="o">-</span> <span class="nx">lastQueriedDate</span><span class="p">.</span><span class="nx">getTime</span><span class="p">()</span> <span class="o">&gt;=</span> <span class="nx">AutoTheme</span><span class="p">.</span><span class="nx">oneDay</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="p">({</span><span class="nx">latitude</span><span class="p">,</span> <span class="nx">longitude</span><span class="p">}</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">getNewLatitudeLongitude</span><span class="p">(</span><span class="nx">currentDate</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">latitude</span> <span class="o">=</span> <span class="nb">Number</span><span class="p">(</span><span class="k">await</span> <span class="nx">AsyncStorage</span><span class="p">.</span><span class="nx">getItem</span><span class="p">(</span><span class="s1">&#39;@Latitude&#39;</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">      <span class="nx">longitude</span> <span class="o">=</span> <span class="nb">Number</span><span class="p">(</span><span class="k">await</span> <span class="nx">AsyncStorage</span><span class="p">.</span><span class="nx">getItem</span><span class="p">(</span><span class="s1">&#39;@Longitude&#39;</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">{</span><span class="nx">latitude</span><span class="p">,</span> <span class="nx">longitude</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span></code></pre></div><p>The final function is used to get the user&rsquo;s current location (latitude and longitude), we then store this
current location in local storage (async storage), alongside the current date. This date is used to check
later on if we need to get the user&rsquo;s location again.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl">  <span class="kr">private</span> <span class="kr">async</span> <span class="nx">getNewLatitudeLongitude</span><span class="p">(</span><span class="nx">currentDate</span>: <span class="kt">Date</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">let</span> <span class="nx">latitude</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">let</span> <span class="nx">longitude</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">granted</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">RNLocation</span><span class="p">.</span><span class="nx">requestPermission</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">      <span class="nx">ios</span><span class="o">:</span> <span class="s1">&#39;whenInUse&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">android</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">detail</span><span class="o">:</span> <span class="s1">&#39;coarse&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">granted</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="kd">let</span> <span class="nx">location</span>: <span class="kt">Location</span> <span class="o">|</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">location</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">RNLocation</span><span class="p">.</span><span class="nx">getLatestLocation</span><span class="p">({</span><span class="nx">timeout</span>: <span class="kt">60000</span><span class="p">});</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">Snackbar</span><span class="p">.</span><span class="nx">show</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">          <span class="nx">title</span><span class="o">:</span> <span class="s1">&#39;Failed to get location, please check it is turned on&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">});</span>
</span></span><span class="line"><span class="cl">        <span class="k">throw</span> <span class="nb">Error</span><span class="p">(</span><span class="s1">&#39;No location found&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="k">if</span> <span class="p">(</span><span class="nx">location</span> <span class="o">!==</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">latitude</span> <span class="o">=</span> <span class="nx">location</span><span class="p">.</span><span class="nx">latitude</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="nx">longitude</span> <span class="o">=</span> <span class="nx">location</span><span class="p">.</span><span class="nx">longitude</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">await</span> <span class="nx">Promise</span><span class="p">.</span><span class="nx">all</span><span class="p">([</span>
</span></span><span class="line"><span class="cl">          <span class="nx">AsyncStorage</span><span class="p">.</span><span class="nx">setItem</span><span class="p">(</span><span class="s1">&#39;@Latitude&#39;</span><span class="p">,</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">latitude</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">          <span class="nx">AsyncStorage</span><span class="p">.</span><span class="nx">setItem</span><span class="p">(</span><span class="s1">&#39;@Longitude&#39;</span><span class="p">,</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">longitude</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">          <span class="nx">AsyncStorage</span><span class="p">.</span><span class="nx">setItem</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="s1">&#39;@LastQueriedLocation&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">currentDate</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">          <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="p">]);</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">latitude</span> <span class="o">===</span> <span class="kc">undefined</span> <span class="o">||</span> <span class="nx">longitude</span> <span class="o">===</span> <span class="kc">undefined</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="k">throw</span> <span class="nb">Error</span><span class="p">(</span><span class="s1">&#39;No location found&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">{</span><span class="nx">latitude</span><span class="p">,</span> <span class="nx">longitude</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span></code></pre></div><h3 id="theme-context">Theme Context</h3>
<p>Next, let&rsquo;s take a look at the module in charge of actually changing our theme and storing the current
theme (used by the other components). We will use React&rsquo;s Context, React Contexts can be used to store the
global state of our application. Such as our current theme, this can then be accessed anywhere in our
application and also changed anywhere.</p>
<blockquote>
<p>Context provides a way to pass data through the component tree without having to pass props down manually at every level. - <a href="https://reactjs.org/docs/context.html">https://reactjs.org/docs/context.html</a></p>
</blockquote>
<p>In our case, we don&rsquo;t want to have to pass the Theme to every component as a prop. So we store it in our a React
context. Firstly, we define some types that will be used in our React context file, such as the light and
dark theme constants.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span><span class="p">,</span> <span class="p">{</span> <span class="nx">Context</span><span class="p">,</span> <span class="nx">createContext</span><span class="p">,</span> <span class="nx">useState</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;react&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">type</span> <span class="nx">ThemeColors</span> <span class="o">=</span> <span class="s2">&#34;#17212D&#34;</span> <span class="o">|</span> <span class="s2">&#34;#FFF&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">interface</span> <span class="nx">ITheme</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">background</span>: <span class="kt">ThemeColors</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">color</span>: <span class="kt">ThemeColors</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">isDark</span>: <span class="kt">boolean</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">LIGHT_THEME</span>: <span class="kt">ITheme</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">background</span><span class="o">:</span> <span class="s2">&#34;#FFF&#34;</span> <span class="kr">as</span> <span class="nx">ThemeColors</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">color</span><span class="o">:</span> <span class="s2">&#34;#17212D&#34;</span> <span class="kr">as</span> <span class="nx">ThemeColors</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">isDark</span>: <span class="kt">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">DARK_THEME</span>: <span class="kt">ITheme</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">background</span><span class="o">:</span> <span class="s2">&#34;#17212D&#34;</span> <span class="kr">as</span> <span class="nx">ThemeColors</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">color</span><span class="o">:</span> <span class="s2">&#34;#FFF&#34;</span> <span class="kr">as</span> <span class="nx">ThemeColors</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">isDark</span>: <span class="kt">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">interface</span> <span class="nx">IThemeContext</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">theme</span>: <span class="kt">ITheme</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">changeTheme</span><span class="o">:</span> <span class="p">(</span><span class="nx">isDark</span>: <span class="kt">boolean</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="k">void</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Next, we create our context with some default values. This then allows us to access the provider
and consumer (<code>ThemeContext.Provider</code>);</p>
<ul>
<li>Provider: The component that will provide the value of the context (stored).</li>
<li>Consumer: The component that will consume the value</li>
</ul>
<p><em>Note</em>: We will not use the consumer part in our app because we are accessing the value
in other ways (React hooks).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">ThemeContext</span>: <span class="kt">Context</span><span class="p">&lt;</span><span class="nt">IThemeContext</span><span class="p">&gt;</span> <span class="o">=</span> <span class="nx">createContext</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nx">changeTheme</span><span class="o">:</span> <span class="p">(</span><span class="nx">_</span>: <span class="kt">boolean</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nx">theme</span>: <span class="kt">LIGHT_THEME</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><p>Now let&rsquo;s define our provider.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">ThemeProvider</span>: <span class="kt">React.FC</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">children</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="p">[</span><span class="nx">themeState</span><span class="p">,</span> <span class="nx">setTheme</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nx">theme</span>: <span class="kt">LIGHT_THEME</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">changeTheme</span> <span class="o">=</span> <span class="p">(</span><span class="nx">isDark</span>: <span class="kt">boolean</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">setTheme</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">      <span class="nx">theme</span>: <span class="kt">isDark</span> <span class="o">?</span> <span class="nx">DARK_THEME</span> : <span class="kt">LIGHT_THEME</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">ThemeContext.Provider</span>
</span></span><span class="line"><span class="cl">      <span class="na">value</span><span class="o">=</span><span class="p">{{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">changeTheme</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">theme</span>: <span class="kt">themeState.theme</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="p">}}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span><span class="nx">children</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">ThemeContext.Provider</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>The <code>useState</code> function is a React hook, which returns the current state <code>themeState</code> and function
to change the state <code>setTheme</code>, in this case, we can pass theme (light theme as default) so that the state
can only be a theme object, cannot change it to say 0.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">const</span> <span class="p">[</span><span class="nx">themeState</span><span class="p">,</span> <span class="nx">setTheme</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nx">theme</span>: <span class="kt">LIGHT_THEME</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><p>Then we define the function that can change our theme, if <code>isDark</code> is <code>true</code> then the
theme becomes dark else it becomes light.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">changeTheme</span> <span class="o">=</span> <span class="p">(</span><span class="nx">isDark</span>: <span class="kt">boolean</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">setTheme</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nx">theme</span>: <span class="kt">isDark</span> <span class="o">?</span> <span class="nx">DARK_THEME</span> : <span class="kt">LIGHT_THEME</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>Finally, we define the actual component for theme provider, it takes in any React component. This way any component
surronded by the provider can access/change the app theme.
We need to give the provider a function to change the value and the value itself.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">ThemeContext.Provider</span>
</span></span><span class="line"><span class="cl">    <span class="na">value</span><span class="o">=</span><span class="p">{{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">changeTheme</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">theme</span>: <span class="kt">themeState.theme</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">}}</span>
</span></span><span class="line"><span class="cl">  <span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="nx">children</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">ThemeContext.Provider</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span></code></pre></div><h3 id="app">App</h3>
<p>We use our provider in the main function</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span> <span class="kr">from</span> <span class="s2">&#34;react&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">ThemeProvider</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;~/providers/ThemeContext&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">MainApp</span> <span class="kr">from</span> <span class="s2">&#34;~/MainApp&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="kr">class</span> <span class="nx">App</span> <span class="kr">extends</span> <span class="nx">React</span><span class="p">.</span><span class="nx">Component</span><span class="o">&lt;</span><span class="p">{},</span> <span class="p">{}</span><span class="o">&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">public</span> <span class="nx">render() {</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">ThemeProvider</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">MainApp</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;/</span><span class="nt">ThemeProvider</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="main-app">Main App</h3>
<p>Now we have the logic to determine if we should change to a dark theme, depending on the time of day.
But how/when do we call this auto theme module, well this is done through the <code>MainApp.tsx</code> module.
Below is a very simple page, with a logo (that changes depending on the theme) a switch
to turn on auto-theme and the current theme displayed i.e. light or dark.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl">  <span class="c1">// Access Theme context within this React class.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="kr">public</span> <span class="kr">static</span> <span class="nx">contextType</span> <span class="o">=</span> <span class="nx">ThemeContext</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kr">public</span> <span class="nx">context</span><span class="o">!:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">ContextType</span><span class="p">&lt;</span><span class="nt">typeof</span> <span class="na">ThemeContext</span><span class="p">&gt;;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Set default state for the class.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="kr">public</span> <span class="nx">state</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">autoTheme</span>: <span class="kt">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">autoToggleTheme</span>: <span class="kt">new</span> <span class="nx">AutoTheme</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">public</span> <span class="nx">render() {</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">theme</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">theme</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">View</span> <span class="na">style</span><span class="o">=</span><span class="p">{{</span><span class="nx">flex</span>: <span class="kt">1</span><span class="p">,</span> <span class="nx">backgroundColor</span>: <span class="kt">theme.background</span><span class="p">}}&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">Header</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">ListItem</span>
</span></span><span class="line"><span class="cl">          <span class="na">containerStyle</span><span class="o">=</span><span class="p">{{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">backgroundColor</span>: <span class="kt">theme.background</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="p">}}</span>
</span></span><span class="line"><span class="cl">          <span class="na">topDivider</span><span class="o">=</span><span class="p">{</span><span class="kc">true</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">          <span class="na">bottomDivider</span><span class="o">=</span><span class="p">{</span><span class="kc">true</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">          <span class="na">titleStyle</span><span class="o">=</span><span class="p">{{</span><span class="nx">color</span>: <span class="kt">theme.color</span><span class="p">}}</span>
</span></span><span class="line"><span class="cl">          <span class="na">title</span><span class="o">=</span><span class="s">&#34;Auto Toggle Dark Theme&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="na">switch</span><span class="o">=</span><span class="p">{{</span>
</span></span><span class="line"><span class="cl">            <span class="nx">onValueChange</span>: <span class="kt">this.autoTheme.bind</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">autoTheme</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="nx">thumbColor</span><span class="o">:</span> <span class="s1">&#39;white&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nx">trackColor</span><span class="o">:</span> <span class="p">{</span><span class="kc">false</span><span class="o">:</span> <span class="s1">&#39;gray&#39;</span><span class="p">,</span> <span class="kc">true</span><span class="o">:</span> <span class="s1">&#39;blue&#39;</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">            <span class="nx">value</span>: <span class="kt">this.state.autoTheme</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="p">}}</span>
</span></span><span class="line"><span class="cl">        <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">View</span> <span class="na">style</span><span class="o">=</span><span class="p">{{</span><span class="nx">flex</span>: <span class="kt">1</span><span class="p">,</span> <span class="nx">justifyContent</span><span class="o">:</span> <span class="s1">&#39;center&#39;</span><span class="p">,</span> <span class="nx">alignItems</span><span class="o">:</span> <span class="s1">&#39;center&#39;</span><span class="p">}}&gt;</span>
</span></span><span class="line"><span class="cl">          <span class="p">&lt;</span><span class="nt">Text</span> <span class="na">style</span><span class="o">=</span><span class="p">{{</span><span class="nx">color</span>: <span class="kt">theme.color</span><span class="p">,</span> <span class="nx">fontSize</span>: <span class="kt">30</span><span class="p">}}&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="nx">Current</span> <span class="nx">Theme</span><span class="o">:</span> <span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">theme</span><span class="p">.</span><span class="nx">isDark</span> <span class="o">?</span> <span class="s1">&#39;Dark&#39;</span> <span class="o">:</span> <span class="s1">&#39;Light&#39;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">          <span class="p">&lt;/</span><span class="nt">Text</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;/</span><span class="nt">View</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;/</span><span class="nt">View</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// ...
</span></span></span></code></pre></div><p><img
        loading="lazy"
        src="/posts/2020-01-25-auto-toggle-dark-theme-on-your-react-native-app/images/main.png"
        type=""
        alt="Main Page"
        
      /></p>
<p>The theme is changed using the line <code>this.context.changeTheme(isDark);</code> essentially sets the theme for the app.
We can then do something like <code>this.context.theme.color</code> to get the current colour or
<code>this.context.theme.background</code> to get the background colour the app should be using.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl">  <span class="c1">// Called when the switch is toggled
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="kr">private</span> <span class="nx">autoTheme</span> <span class="o">=</span> <span class="kr">async</span> <span class="p">(</span><span class="nx">value</span>: <span class="kt">boolean</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">this</span><span class="p">.</span><span class="nx">setState</span><span class="p">({</span><span class="nx">autoTheme</span>: <span class="kt">value</span><span class="p">});</span>
</span></span><span class="line"><span class="cl">    <span class="kd">let</span> <span class="nx">isDark</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">isDark</span> <span class="o">=</span> <span class="k">await</span> <span class="k">new</span> <span class="nx">AutoTheme</span><span class="p">().</span><span class="nx">shouldToggleDarkTheme</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">changeTheme</span><span class="p">(</span><span class="nx">isDark</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// ...
</span></span></span></code></pre></div><p>The other key function is this one, where we listen for when the app goes from background
to the foreground, if this happens we then call the auto theme module and check if we should
toggle the theme, say you do this between sunsets. You background the app at 6.58 PM, the
sunsets at 7.0 2PM and you foreground the app at 7.04 PM then when the user returns
<code>this.context.changeTheme(true)</code> will be called like this (true) and then the values
returned by <code>this.context.theme</code> would change to the dark theme.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{...,</span> <span class="nx">AppState</span><span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;react-native&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">public</span> <span class="kr">async</span> <span class="nx">componentDidMount() {</span>
</span></span><span class="line"><span class="cl">    <span class="nx">AppState</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;change&#39;</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">appInFocus</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">public</span> <span class="nx">componentWillUnmount() {</span>
</span></span><span class="line"><span class="cl">    <span class="nx">AppState</span><span class="p">.</span><span class="nx">removeEventListener</span><span class="p">(</span><span class="s1">&#39;change&#39;</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">appInFocus</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">private</span> <span class="nx">appInFocus</span> <span class="o">=</span> <span class="kr">async</span> <span class="p">(</span><span class="nx">nextAppState</span>: <span class="kt">any</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">nextAppState</span> <span class="o">===</span> <span class="s1">&#39;active&#39;</span> <span class="o">&amp;&amp;</span> <span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">autoTheme</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="kr">const</span> <span class="nx">isDark</span> <span class="o">=</span> <span class="k">await</span> <span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">autoToggleTheme</span><span class="p">.</span><span class="nx">shouldToggleDarkTheme</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">      <span class="k">this</span><span class="p">.</span><span class="nx">context</span><span class="p">.</span><span class="nx">changeTheme</span><span class="p">(</span><span class="nx">isDark</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span></code></pre></div><h3 id="header">Header</h3>
<p>We have a single component in the MainApp page, which is a header, the header
will change logos depending on what the current theme is (again using context).
Here we are using React Hooks with our React Theme Context. Without needing to pass the theme as a prop.
This is particularly useful is this component was a is few levels deep, without
the hook we would need to keep passing the theme as a prop
to the child of a component from the parent, then that component would pass it to it&rsquo;s
child etc.</p>
<p>The context allows us have a global state throughout our app and
the hooks allow us to access this state without needing to turn our components into a class.
Though as you have seen we can also access the context within our React classes.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">Header</span> <span class="kr">as</span> <span class="nx">ElementsHeader</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;react-native-elements&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">logoDark</span> <span class="kr">from</span> <span class="s2">&#34;~/assets/images/logo-dark.png&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">logoLight</span> <span class="kr">from</span> <span class="s2">&#34;~/assets/images/logo-light.png&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">ThemeContext</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;~/providers/ThemeContext&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">Header</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="p">{</span> <span class="nx">background</span><span class="p">,</span> <span class="nx">color</span><span class="p">,</span> <span class="nx">isDark</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">useContext</span><span class="p">(</span><span class="nx">ThemeContext</span><span class="p">).</span><span class="nx">theme</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">ElementsHeader</span>
</span></span><span class="line"><span class="cl">      <span class="na">containerStyle</span><span class="o">=</span><span class="p">{{</span> <span class="nx">backgroundColor</span>: <span class="kt">background</span> <span class="p">}}</span>
</span></span><span class="line"><span class="cl">      <span class="na">centerComponent</span><span class="o">=</span><span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">View</span> <span class="na">style</span><span class="o">=</span><span class="p">{{</span> <span class="nx">flexDirection</span><span class="o">:</span> <span class="s2">&#34;row&#34;</span><span class="p">,</span> <span class="nx">flexWrap</span><span class="o">:</span> <span class="s2">&#34;wrap&#34;</span> <span class="p">}}&gt;</span>
</span></span><span class="line"><span class="cl">          <span class="p">&lt;</span><span class="nt">Text</span> <span class="na">style</span><span class="o">=</span><span class="p">{{</span> <span class="nx">color</span> <span class="p">}}&gt;</span><span class="nx">Example</span><span class="p">&lt;/</span><span class="nt">Text</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">          <span class="p">&lt;</span><span class="nt">Image</span>
</span></span><span class="line"><span class="cl">            <span class="na">source</span><span class="o">=</span><span class="p">{</span><span class="nx">isDark</span> <span class="o">?</span> <span class="nx">logoLight</span> : <span class="kt">logoDark</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="na">style</span><span class="o">=</span><span class="p">{{</span> <span class="nx">height</span>: <span class="kt">25</span><span class="p">,</span> <span class="nx">width</span>: <span class="kt">25</span> <span class="p">}}</span>
</span></span><span class="line"><span class="cl">          <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">          <span class="p">&lt;</span><span class="nt">Text</span> <span class="na">style</span><span class="o">=</span><span class="p">{{</span> <span class="nx">color</span> <span class="p">}}&gt;</span><span class="nx">App</span><span class="p">&lt;/</span><span class="nt">Text</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;/</span><span class="nt">View</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><h2 id="run-the-app">Run the app</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone git@gitlab.com:hmajid2301/articles.git
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> <span class="s2">&#34;articles/19. Theme your React Native app/ExampleApp&#34;</span>
</span></span><span class="line"><span class="cl">yarn
</span></span><span class="line"><span class="cl">yarn run start
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Then in another terminal</span>
</span></span><span class="line"><span class="cl">yarn run android
</span></span></code></pre></div><h2 id="example-app">Example App</h2>
<p>Here is a GIF of the app running.</p>
<p><img
        loading="lazy"
        src="/posts/2020-01-25-auto-toggle-dark-theme-on-your-react-native-app/images/demo.gif"
        type=""
        alt="Demo App"
        
      /></p>
<h2 id="appendix">Appendix</h2>
<p>That&rsquo;s it we successfully created an app that auto changes the user&rsquo;s theme depending on
the time of day, using the user&rsquo;s location to determine sunrise and sunset.</p>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2020-01-25-auto-toggle-dark-theme-on-your-react-native-app/source_code">Example source code</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Using Tox with a Makefile to Automate Python related tasks</title>
      <link>https://haseebmajid.dev/posts/2020-01-13-using-tox-with-a-makefile-to-automate-python-related-tasks/</link>
      <pubDate>Mon, 13 Jan 2020 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2020-01-13-using-tox-with-a-makefile-to-automate-python-related-tasks/</guid>
      <description>&lt;p&gt;In this article, we will go over how we can use a makefile and tox to automate various Python related tools.
This article assumes you are running bash (or equivalent).&lt;/p&gt;
&lt;h2 id=&#34;tox&#34;&gt;Tox&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://tox.readthedocs.io/en/latest/&#34;&gt;Tox&lt;/a&gt; is an automation tool used primarily to add in testing.
On the Tox website, it describes itself as&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;tox aims to automate and standardize testing in Python. It is part of a larger vision of easing the packaging, testing and release process of Python software.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this article, we will go over how we can use a makefile and tox to automate various Python related tools.
This article assumes you are running bash (or equivalent).</p>
<h2 id="tox">Tox</h2>
<p><a href="https://tox.readthedocs.io/en/latest/">Tox</a> is an automation tool used primarily to add in testing.
On the Tox website, it describes itself as</p>
<blockquote>
<p>tox aims to automate and standardize testing in Python. It is part of a larger vision of easing the packaging, testing and release process of Python software.</p>
</blockquote>
<p>You define a configuration file <code>tox.ini</code> where you define all of your tox environments. In the example below,we have two environments, <code>testenv</code> to run our tests
and <code>testenv:lint</code> to lint our code with Flake8.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[tox]</span>
</span></span><span class="line"><span class="cl"><span class="na">envlist</span> <span class="o">=</span> <span class="s">py36,py37,lint</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[testenv]</span>
</span></span><span class="line"><span class="cl"><span class="na">basepython</span> <span class="o">=</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">    {lint}: {env:TOXPYTHON:python3}
</span></span></span><span class="line"><span class="cl"><span class="s">    py36: {env:TOXPYTHON:python3.6}
</span></span></span><span class="line"><span class="cl"><span class="s">    py37: {env:TOXPYTHON:python3.7}</span>
</span></span><span class="line"><span class="cl"><span class="na">passenv</span> <span class="o">=</span> <span class="s">*</span>
</span></span><span class="line"><span class="cl"><span class="na">install_command</span> <span class="o">=</span> <span class="s">pip install {opts} {packages}</span>
</span></span><span class="line"><span class="cl"><span class="na">deps</span> <span class="o">=</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">    pytest
</span></span></span><span class="line"><span class="cl"><span class="s">    pytest-mock</span>
</span></span><span class="line"><span class="cl"><span class="na">usedevelop</span> <span class="o">=</span> <span class="s">false</span>
</span></span><span class="line"><span class="cl"><span class="na">commands</span> <span class="o">=</span> <span class="s">pytest -v {posargs} tests</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[testenv:lint]</span>
</span></span><span class="line"><span class="cl"><span class="na">skip_install</span> <span class="o">=</span> <span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">deps</span> <span class="o">=</span> <span class="s">flake8</span>
</span></span><span class="line"><span class="cl"><span class="na">commands</span> <span class="o">=</span> <span class="s">flake8 src/</span>
</span></span></code></pre></div><p>How Tox works is that it created a virtual environment (virtualenv) for each tox environment defined in the configuration file (<code>tox.ini</code>).
It then runs our command within that virtualenv, you can see these if you take a look in the <code>.tox</code> folder.
So in our lint example, it would create a virtualenv called lint in the .tox folder, install our dependencies <code>flake8</code> and finally run the command
<code>flake8 src/</code> (within the lint virtualenv). You can read more about how Tox works <a href="https://tox.readthedocs.io/en/latest/#system-overview">over here</a>.
So how do we run a tox environment, like so;</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Install Tox</span>
</span></span><span class="line"><span class="cl">pip install tox
</span></span><span class="line"><span class="cl"><span class="c1"># Run the tox environment</span>
</span></span><span class="line"><span class="cl">tox -e lint
</span></span></code></pre></div><p>We can pass extra parameters to tox environments using the <code>{posargs}</code>. So for example, if we had an environment defined as</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[testenv:bumpversion]</span>
</span></span><span class="line"><span class="cl"><span class="na">skip_install</span> <span class="o">=</span> <span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">deps</span> <span class="o">=</span> <span class="s">bumpversion</span>
</span></span><span class="line"><span class="cl"><span class="na">commands</span> <span class="o">=</span> <span class="s">bumpversion --verbose {posargs}</span>
</span></span></code></pre></div><p>We could run it like so <code>tox -e bumpversion -- --allow-dirty patch</code> (note the extra <code>--</code>).</p>
<p>So as you can see Tox allows us to automate tedious Python related tasks such as code formatting, running the lint and running unit tests.
We can test our against using different versions of Python as well such as Python3.6 or Python3.7, to make sure our code is compatible with
both. So if we wanted to run pytest against python3.6 we could do it like so <code>tox -e py36</code> and equally python3.7 as <code>tox -e py37</code>
(given the same configuration file as above). Some common tools Tox is used in conjunction with include;</p>
<ul>
<li><a href="https://github.com/psf/black">Black</a></li>
<li><a href="https://github.com/timothycrosley/isort">Isort</a></li>
<li><a href="https://github.com/timothycrosley/isort">Pytest</a></li>
<li><a href="https://github.com/PyCQA/flake8">Flake8</a></li>
<li><a href="https://github.com/peritus/bumpversion">Bumpversion</a></li>
<li><a href="https://github.com/pypa/twine">Twine</a></li>
</ul>
<h2 id="makefile">Makefile</h2>
<p>Makefiles are often used in C/C++ programs to compile the code/generate binaries etc. Used to automate (often long-winded) tasks.
To use a make file all you need to do is create a file called <code>Makefile</code>. Each &ldquo;job&rdquo; in the makefile is called a <code>target</code>, for
example a makefile may look like so;</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-makefile" data-lang="makefile"><span class="line"><span class="cl"><span class="nv">PY</span> <span class="o">=</span> py36
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># prompt_example&gt; make test PY=py36 OPTIONS=&#34;-- -s&#34;
</span></span></span><span class="line"><span class="cl"><span class="c"></span><span class="nf">.PHONY</span><span class="o">:</span> <span class="n">test</span>
</span></span><span class="line"><span class="cl"><span class="nf">test</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">	@tox -e <span class="k">$(</span>PY<span class="k">)</span> <span class="k">$(</span>OPTIONS<span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">.PHONY</span><span class="o">:</span> <span class="n">lint</span>
</span></span><span class="line"><span class="cl"><span class="nf">lint</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">	@tox -e lint
</span></span></code></pre></div><p>So now if we want to run our linter we could simply do <code>make lint</code>, to run our tests we can simply type the command <code>make test</code>.
If we want to specify a Python version we could do <code>make test PY=py37</code> (note how <code>$(PY)</code> is a variable
we can override). This may remind of tools available to other languages such as <code>package.json</code> for JavaScript/NodeJS.
The main advantage of using a Makefile with Tox is that we can define targets in our makefile that aren&rsquo;t specifically
related to Python tools. Such as cleaning our project.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-makefile" data-lang="makefile"><span class="line"><span class="cl"><span class="nf">.PHONY</span><span class="o">:</span> <span class="n">clean</span>
</span></span><span class="line"><span class="cl"><span class="nf">clean</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">	@find . -type f -name <span class="s1">&#39;*.pyc&#39;</span> -delete
</span></span><span class="line"><span class="cl">	@find . -type d -name <span class="s1">&#39;__pycache__&#39;</span> <span class="p">|</span> xargs rm -rf
</span></span><span class="line"><span class="cl">	@find . -type d -name <span class="s1">&#39;*.ropeproject&#39;</span> <span class="p">|</span> xargs rm -rf
</span></span><span class="line"><span class="cl">	@rm -rf build/
</span></span><span class="line"><span class="cl">	@rm -rf dist/
</span></span><span class="line"><span class="cl">	@rm -f src/*.egg*
</span></span><span class="line"><span class="cl">	@rm -f MANIFEST
</span></span><span class="line"><span class="cl">	@rm -rf docs/build/
</span></span><span class="line"><span class="cl">	@rm -f .coverage.*
</span></span></code></pre></div><p>That&rsquo;s is a simple introduction how you can use a <code>Makefile</code> and <code>Tox</code> in conjunction to automate various
tedious tasks.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/gitlab-automation-toolkit/gitlab-auto-release/tree/abfdd70e1dae8bacf7dfd999a76711ca052ce23e">Example Project</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Better Imports with Typescript Aliases, Babel and TSPath</title>
      <link>https://haseebmajid.dev/posts/2019-12-01-better-imports-with-typescript-aliases-babel-and-tspath/</link>
      <pubDate>Sun, 01 Dec 2019 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2019-12-01-better-imports-with-typescript-aliases-babel-and-tspath/</guid>
      <description>&lt;p&gt;In this article, I will explain how you can use typescript aliases with Babel or TSPath.
If you have been using TypeScript/JavaScript (TS/JS) and have a nested folder structure,
you may well be used to seeing imports like so (using es6 style imports). This is sometimes
referred to as &lt;strong&gt;path hell&lt;/strong&gt; and is a very common occurrence as your project grows in size.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;moduleA&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;../../../moduleA&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;moduleB&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;../moduleB&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;These are called relative imports, as we are importing modules using paths relative
to our current module/file. As you can see, they can sometimes be very ugly and hard to work out
where the module is we are importing. So sometimes you will use the wrong number of &amp;ldquo;../&amp;rdquo; etc.
There are a few tools we can use to help solve our problem.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this article, I will explain how you can use typescript aliases with Babel or TSPath.
If you have been using TypeScript/JavaScript (TS/JS) and have a nested folder structure,
you may well be used to seeing imports like so (using es6 style imports). This is sometimes
referred to as <strong>path hell</strong> and is a very common occurrence as your project grows in size.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">moduleA</span> <span class="nx">from</span> <span class="s2">&#34;../../../moduleA&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">moduleB</span> <span class="nx">from</span> <span class="s2">&#34;../moduleB&#34;</span><span class="p">;</span>
</span></span></code></pre></div><p>These are called relative imports, as we are importing modules using paths relative
to our current module/file. As you can see, they can sometimes be very ugly and hard to work out
where the module is we are importing. So sometimes you will use the wrong number of &ldquo;../&rdquo; etc.
There are a few tools we can use to help solve our problem.</p>
<h2 id="structure">Structure</h2>
<p>In the examples below let&rsquo;s assume we have a structure which looks something like this.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">├── app.json
</span></span><span class="line"><span class="cl">├── babel.config.js
</span></span><span class="line"><span class="cl">├── App.tsx
</span></span><span class="line"><span class="cl">├── README.md
</span></span><span class="line"><span class="cl">├── src
</span></span><span class="line"><span class="cl">│   ├── actions
</span></span><span class="line"><span class="cl">│   ├── assets
</span></span><span class="line"><span class="cl">│   ├── components
</span></span><span class="line"><span class="cl">│   │   ├── AppHeader
</span></span><span class="line"><span class="cl">│   │   │   ├── AppHeader.tsx
</span></span><span class="line"><span class="cl">│   │   │   ├── index.ts
</span></span><span class="line"><span class="cl">│   │   │   └── styles.tsx
</span></span><span class="line"><span class="cl">│   │   ├── Logo
</span></span><span class="line"><span class="cl">│   │   │   ├── index.ts
</span></span><span class="line"><span class="cl">│   │   │   ├── Logo.tsx
</span></span><span class="line"><span class="cl">│   │   │   └── styles.tsx
</span></span><span class="line"><span class="cl">│   │   └── PhotoAlbumList
</span></span><span class="line"><span class="cl">│   │       ├── index.ts
</span></span><span class="line"><span class="cl">│   │       ├── PhotoAlbumList.tsx
</span></span><span class="line"><span class="cl">│   │       └── styles.tsx
</span></span><span class="line"><span class="cl">│   └── views
</span></span><span class="line"><span class="cl">│       ├── AboutUs.tsx
</span></span><span class="line"><span class="cl">│       ├── FAQ.tsx
</span></span><span class="line"><span class="cl">│       ├── Home.tsx
</span></span><span class="line"><span class="cl">│       └── Settings.tsx
</span></span><span class="line"><span class="cl">├── tsconfig.json
</span></span><span class="line"><span class="cl">├── tslint.json
</span></span><span class="line"><span class="cl">└── yarn.lock
</span></span></code></pre></div><h2 id="typescript-aliases">TypeScript Aliases</h2>
<p>In TS there is an option we can set in our config file <code>tsconfig.json</code>, referred to as TS aliases.
Let&rsquo;s take a look at an example to see what it can do. Let&rsquo;s say we&rsquo;re in the <code>Home.tsx</code> file and we want
to import Logo at the moment we would do something like (in this case index.ts, exports the Logo hence
we don&rsquo;t have to go <code>../components/Logo/Logo</code>.)</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="c1">// without TS aliases
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">import</span> <span class="nx">Logo</span> <span class="nx">from</span> <span class="s2">&#34;../components/Logo&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// with TS aliases
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">import</span> <span class="nx">Logo</span> <span class="nx">from</span> <span class="s2">&#34;~/components/Logo&#34;</span><span class="p">;</span>
</span></span></code></pre></div><p>Anytime we use the <code>~</code> character in our imports it automatically starts importing from the <code>src</code> folder.
I think this makes our imports far easier to follow and read. You can also change the TS aliases
so you can have one for the components folder like @components or actions like @actions. It&rsquo;s all up to you how
you want to structure your project.</p>
<h3 id="tsconfigjson">tsconfig.json</h3>
<p>Now I&rsquo;ve shown you what TS aliases are, but how do we add them to our project? Simple, open your <code>tsconfig.json</code> file and
add the following two options</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;baseUrl&#34;</span><span class="p">:</span> <span class="s2">&#34;.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;paths&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;~/*&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;src/*&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The baseUrl means we use the root directory (the directory where <code>tsconfig.json</code> is), and look for the <code>src</code> folder in the
same directory.</p>
<h2 id="babel-module-resolver">Babel Module Resolver</h2>
<p>Now if you start to use <code>~</code> in your imports, you shouldn&rsquo;t see TS raise any issues/problems. However, if you
transpile TS into JS, you&rsquo;ll notice you still have <code>~</code> in your imports. Our imports do not automatically get changed.
Hence earlier I suggested you could use the Babel module resolver.</p>
<p>One tool that works very well is the <a href="https://github.com/tleunen/babel-plugin-module-resolver">Babel module resolver</a>. However,
you need to be using <a href="https://babeljs.io/">Babel</a>, Babel is a tool which is used to transpile &ldquo;new JS&rdquo;
into plain old ES5 JS.</p>
<p>I will assume you already have Babel setup. If you&rsquo;re using say React Native and you created the project by using the cli tool, Babel
already comes configured. What you&rsquo;ll need to do from there is install the plugin.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">yarn add --dev babel-plugin-module-resolver
</span></span><span class="line"><span class="cl"><span class="c1"># or</span>
</span></span><span class="line"><span class="cl">npm install --save-dev babel-plugin-module-resolver
</span></span></code></pre></div><p>Then add the following to your Babel configuration file, which will either be something like <code>.babelrc</code>, <code>babel.config.js</code> or <code>.babelrc.js</code>.
You can also place your configuration in the <code>package.json</code> file using the <code>babel</code> key.</p>
<p>If your configuration file is a JS file (ends in <code>.js</code>)</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="p">...</span>
</span></span><span class="line"><span class="cl">  <span class="nx">plugins</span><span class="o">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;module-resolver&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">alias</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="s2">&#34;~&#34;</span><span class="o">:</span> <span class="s2">&#34;./src&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>If your configuration file is a JSON file.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="nt">&#34;plugins&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;module-resolver&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;alias&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nt">&#34;~&#34;</span><span class="p">:</span> <span class="s2">&#34;./src&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The module resolver will now automatically be run every time Babel is run. If you&rsquo;re using React Native,
this is already done for us.</p>
<h2 id="tspath">TSPath</h2>
<p>We cannot always include Babel in our projects, in this case I recommend using
<a href="https://www.npmjs.com/package/tspath">TSPath</a>. For example, I had issues getting Babel
to work with my Firebase Cloud Functions project, so I ended up using TSPath for that.</p>
<p>We use TSPath to solve the same issue as Babel module resolver, when TS -&gt; (transpiled) to JS, JS
won&rsquo;t be able to resolve the import paths. First, let&rsquo;s install TSPath.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">yarn add --dev tspath
</span></span><span class="line"><span class="cl"><span class="c1"># or</span>
</span></span><span class="line"><span class="cl">npm install --save-dev tspath
</span></span></code></pre></div><p>Then we run <code>yarn run tspath</code>, then our path aliases become relative paths again.
If your TS gets transpiled say because it&rsquo;s a package being published to NPM, you can add as part
of your build process, for example in my <code>package.json</code> I have the following</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;scripts&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="nt">&#34;build&#34;</span><span class="p">:</span> <span class="s2">&#34;tsc -p . &amp;&amp; npm run fix-paths&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;fix-paths&#34;</span><span class="p">:</span> <span class="s2">&#34;tspath -f&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>That&rsquo;s it! We have now used TS path aliases with our project. I have shown how you can solve the
<strong>path hell</strong> issue in our TS project.</p>
<h2 id="jest">Jest</h2>
<p>If you have tests written in Jest you can also have paths like the above resolve. First you need to edit your jest.config.js file (or equivalent configuration file). Then add the following below (to have the same paths as above).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">moduleNameMapper</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s1">&#39;~/(.*)&#39;</span><span class="o">:</span> <span class="s1">&#39;&lt;rootDir&gt;/src/$1&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">....</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>Then in our tests we can do the following, to import our dependencies</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">AboutList</span> <span class="nx">from</span> <span class="s2">&#34;~/components/AboutList&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">about</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;~/data&#34;</span><span class="p">;</span>
</span></span></code></pre></div><h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/stegappasaurus/tree/cde1afd6fbb9d882bccb9e05693824587ce1b77e">Example project using Babel</a></li>
<li><a href="https://gitlab.com/hmajid2301/stegappasaurus-api/tree/2ed66fd277a148a1e11ad7c3ee932d64afdd242f/functions">Example project using TSPath</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Add adaptive icons to your Android app</title>
      <link>https://haseebmajid.dev/posts/2019-11-09-add-adaptive-icons-to-your-android-app/</link>
      <pubDate>Sat, 09 Nov 2019 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2019-11-09-add-adaptive-icons-to-your-android-app/</guid>
      <description>&lt;p&gt;In this article, we will go over how you can use add the new adaptive app icons to your Android app.
In his article I will be using a React Native project, so the structure of your Android app may vary.&lt;/p&gt;
&lt;p&gt;
  &lt;img
    loading=&#34;lazy&#34;
    src=&#34;https://developer.android.com/guide/practices/ui_guidelines/images/NB_Icon_Mask_Shapes_Ext_02.gif&#34;
    alt=&#34;Mask&#34;
    
  /&gt;&lt;/p&gt;
&lt;h2 id=&#34;adaptive-icons&#34;&gt;Adaptive Icons&lt;/h2&gt;
&lt;p&gt;Adaptive icons are a new feature introduced in Android 8.0 (API level 26). It allows your app icon
to be displayed using multiple shapes across different devices and launchers, more
information available &lt;a href=&#34;https://developer.android.com/guide/practices/ui_guidelines/icon_design_adaptive&#34;&gt;here&lt;/a&gt;.
The main advantage of using adaptive icons is so that we don&amp;rsquo;t have to have to create multiple icons, i.e. round and square
versions of your app icon. You provide two layers, a foreground and the background which is usually just a colour.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this article, we will go over how you can use add the new adaptive app icons to your Android app.
In his article I will be using a React Native project, so the structure of your Android app may vary.</p>
<p>
  <img
    loading="lazy"
    src="https://developer.android.com/guide/practices/ui_guidelines/images/NB_Icon_Mask_Shapes_Ext_02.gif"
    alt="Mask"
    
  /></p>
<h2 id="adaptive-icons">Adaptive Icons</h2>
<p>Adaptive icons are a new feature introduced in Android 8.0 (API level 26). It allows your app icon
to be displayed using multiple shapes across different devices and launchers, more
information available <a href="https://developer.android.com/guide/practices/ui_guidelines/icon_design_adaptive">here</a>.
The main advantage of using adaptive icons is so that we don&rsquo;t have to have to create multiple icons, i.e. round and square
versions of your app icon. You provide two layers, a foreground and the background which is usually just a colour.</p>
<h3 id="android-studio">Android Studio</h3>
<p>To add adaptive icons to your application do the following;</p>
<ul>
<li>Open your application in Android Studio (If it&rsquo;s a React Native application make sure to open the <code>android</code> folder in Android Studio)</li>
<li>Right-click on the <code>app</code> folder</li>
<li>File &gt; New &gt; Image Asset</li>
<li>On the first tab &ldquo;Foreground Layer&rdquo; click on <code>path</code> and select your icon (resize the icon as required)</li>
<li>Select the second tab &ldquo;Background Layer&rdquo; select <code>Asset Type: Color</code> and select the colour you would like to use</li>
<li>Select <code>Next</code> (bottom right)</li>
<li>Select <code>Finish</code></li>
</ul>
<p><img
        loading="lazy"
        src="/posts/2019-11-09-add-adaptive-icons-to-your-android-app/images/android-studio.gif"
        type=""
        alt="Android Studio"
        
      /></p>
<p>You can find the new files in <code>android/app/src/main/res</code> sub-folders</p>
<h3 id="test">Test</h3>
<p>You can test it works, if you&rsquo;re using the example source code found below.</p>
<p>Run the following commands</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">adb connect xxx.xx.xxx.xx <span class="c1"># IP Address of your emulator</span>
</span></span><span class="line"><span class="cl">yarn
</span></span><span class="line"><span class="cl">yarn run start
</span></span><span class="line"><span class="cl">yarn run android <span class="c1"># In another terminal</span>
</span></span></code></pre></div><p>Then you should see something like the image below instead of the default green Android robot.</p>
<p><img
        loading="lazy"
        src="/posts/2019-11-09-add-adaptive-icons-to-your-android-app/images/cover.png"
        type=""
        alt="New Icon"
        
      /></p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2019-11-09-add-adaptive-icons-to-your-android-app/source_code">Example source code</a></li>
<li><a href="https://romannurik.github.io/AndroidAssetStudio/index.html">Android Asset Studio</a></li>
<li><a href="https://www.uplabs.com/posts/adaptive-icons-for-android-o">Cover Photo from UpLabs</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Auto Publish React Native App to Android Play Store using GitLab CI</title>
      <link>https://haseebmajid.dev/posts/2019-10-09-auto-publish-react-native-app-to-android-play-store-using-gitlab-ci/</link>
      <pubDate>Wed, 09 Oct 2019 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2019-10-09-auto-publish-react-native-app-to-android-play-store-using-gitlab-ci/</guid>
      <description>&lt;p&gt;In this article, I will show you how can automate the publishing of your AAB/APK to the &lt;code&gt;Google Play Console&lt;/code&gt;.
We will be using the &lt;a href=&#34;https://github.com/Triple-T/gradle-play-publisher&#34;&gt;Gradle Play Publisher&lt;/a&gt; (GPP) plugin to do
automate this process for us. Using this plugin we cannot only automate the publishing and release of our app,
we can also update the release notes, store listing (including photos) all from GitLab CI.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; In this article I will assume that you are using Linux and React Native version &amp;gt;= 0.60.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this article, I will show you how can automate the publishing of your AAB/APK to the <code>Google Play Console</code>.
We will be using the <a href="https://github.com/Triple-T/gradle-play-publisher">Gradle Play Publisher</a> (GPP) plugin to do
automate this process for us. Using this plugin we cannot only automate the publishing and release of our app,
we can also update the release notes, store listing (including photos) all from GitLab CI.</p>
<p><strong>Note:</strong> In this article I will assume that you are using Linux and React Native version &gt;= 0.60.</p>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li><a href="https://play.google.com/apps/publish/signup/#EnterDetailsPlace">Google Developers Account</a></li>
<li>A working React Native Android project.</li>
</ul>
<h2 id="google-play-console">Google Play Console</h2>
<p>First, we need to create a service account, this account will be used to make changes to our app
automatically, such as publish it or change the store listing.</p>
<ul>
<li>Go to the <a href="https://play.google.com/apps/publish">Google Play Console</a></li>
<li>Go to &ldquo;Settings&rdquo;</li>
<li>Go to &ldquo;Developer Account&rdquo;</li>
<li>Select &ldquo;API access&rdquo;</li>
<li>Click the &ldquo;Create Service Account&rdquo; button</li>
<li>Click the &ldquo;Google API Console&rdquo; button, this will take you to the <code>Google Cloud Platform</code></li>
<li>Click &ldquo;Create Service Account&rdquo;, give your service account a <code>name</code> and a <code>description</code></li>
<li>Select the <code>Role</code> as <code>Owner</code>, select the &ldquo;Continue&rdquo; button and then finally select &ldquo;Done&rdquo;</li>
<li>After creating your service account you should have a file automatically downloading <code>api&lt;...&gt;.json</code>, if not click on &ldquo;Actions (3 Dots) &gt; Create Key&rdquo;, as shown in <code>Image 1</code></li>
<li>Now go back to &ldquo;Play Console&rdquo;</li>
<li>Go to &ldquo;Service Accounts&rdquo; (on the current page), and select &ldquo;Grant Access&rdquo;</li>
<li>Next set the permission as shown in <code>Image 2</code></li>
</ul>
<p>If you would like, you can set the permissions either globally so you can use this account for all your apps,
or you can specify an app so that you will create a new account each time you want to auto-publish a new app.
Each approach has its own advantages, the first approach is more convenient. However, the second approach is
more safe, for example, if your credentials are leaked, only one of your apps is affected.</p>
<p><img
        loading="lazy"
        src="/posts/2019-10-09-auto-publish-react-native-app-to-android-play-store-using-gitlab-ci/images/json-key.png"
        type=""
        alt="Image 1: Permissions"
        
      /></p>
<p><img
        loading="lazy"
        src="/posts/2019-10-09-auto-publish-react-native-app-to-android-play-store-using-gitlab-ci/images/permissions.png"
        type=""
        alt="Image 2: Permissions"
        
      /></p>
<h2 id="gitlab">GitLab</h2>
<p>Now we need the JSON file we just downloaded to be accessible by our GitLab CI jobs, where the JSON file should
look something like this;</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;service_account&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;project_id&#34;</span><span class="p">:</span> <span class="s2">&#34;xxx&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;private_key_id&#34;</span><span class="p">:</span> <span class="s2">&#34;xxx&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;private_key&#34;</span><span class="p">:</span> <span class="s2">&#34;xxx&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;client_email&#34;</span><span class="p">:</span> <span class="s2">&#34;xxx&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;client_id&#34;</span><span class="p">:</span> <span class="s2">&#34;xxx&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;auth_uri&#34;</span><span class="p">:</span> <span class="s2">&#34;https://accounts.google.com/o/oauth2/auth&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;token_uri&#34;</span><span class="p">:</span> <span class="s2">&#34;https://oauth2.googleapis.com/token&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;auth_provider_x509_cert_url&#34;</span><span class="p">:</span> <span class="s2">&#34;https://www.googleapis.com/oauth2/v1/certs&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;client_x509_cert_url&#34;</span><span class="p">:</span> <span class="s2">&#34;xxx&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Now, let&rsquo;s move the relevant keystore information to GitLab CI variables, so we can access them during our CI jobs.
First, go to your GitLab project;</p>
<ul>
<li>Settings &gt; CI/CD &gt; Variables</li>
<li>Add Type: File, Key: PLAY_STORE_JSON, Value: (the contents of your JSON file)</li>
</ul>
<p><strong>Note:</strong> When I&rsquo;m testing locally sometimes I store this JSON file locally, so I include it in my <code>.gitignore</code> (I call mine <code>play-store.json</code>) file
so it doesn&rsquo;t accidentally get published online.</p>
<h2 id="buildgradle">build.gradle</h2>
<p>We need to edit the <code>android/build.gradle</code> file first. The Maven URL is a place where we can download
the GPP plugin. The dependencies list the plugins we want to download and their versions.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-groovy" data-lang="groovy"><span class="line"><span class="cl"><span class="n">buildscript</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl">    <span class="n">repositories</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span>
</span></span><span class="line"><span class="cl">        <span class="n">maven</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">url</span> <span class="s2">&#34;https://plugins.gradle.org/m2/&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="o">}</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">dependencies</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">classpath</span> <span class="s2">&#34;com.android.tools.build:gradle:3.5.0&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="n">classpath</span> <span class="s2">&#34;com.github.triplet.gradle:play-publisher:2.4.1&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></div><h3 id="gradle-wrapperproperties">gradle-wrapper.properties</h3>
<p>To use GPP version <code>2.4.1</code> we need to use gradle version &gt;= 5.6.1. To do this we open
<code>android/gradle/wrapper/gradle-wrapper.properties</code> and edit the line <code>distributionUrl</code> so that the gradle version match
the required version for example <code>distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.1-all.zip</code>.</p>
<h2 id="appbuildgradle">app/build.gradle</h2>
<p>We also need to edit our <code>android/app/build.gradle</code>.
Add the <code>apply plugin: &quot;com.github.triplet.play&quot;</code> to the top of your file after <code>apply plugin: &quot;com.android.application&quot;</code>.
Then at the bottom of the file add the code below. This is what the GPP plugin uses to determine how to publish your app.
In this case, it will</p>
<ul>
<li>defaultToAppBundles: It will generate an AAB (Android App Bundle) instead of APK</li>
<li>track: Which track we should deploy the new AAB to, you can chose for example production or beta, in this case it&rsquo;s the internal track</li>
<li>serviceAccountCredentials: The location of the Play Store JSON we stored in our CI/CD variables in this example it will be kept in <code>android/app/play-store.json</code></li>
<li>resolutionStrategy: Automatically updates the app&rsquo;s version code to matches the next available number required so it&rsquo;s published</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-groovy" data-lang="groovy"><span class="line"><span class="cl"><span class="n">play</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">defaultToAppBundles</span> <span class="o">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">    <span class="n">track</span> <span class="o">=</span> <span class="s2">&#34;internal&#34;</span> <span class="c1">// beta, alpha, production.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">serviceAccountCredentials</span> <span class="o">=</span> <span class="n">file</span><span class="o">(</span><span class="s2">&#34;./play-store.json&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">resolutionStrategy</span> <span class="o">=</span> <span class="s2">&#34;auto&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">outputProcessor</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">versionNameOverride</span> <span class="o">=</span> <span class="s2">&#34;$versionNameOverride.$versionCode&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></div><p>There are numerous more options you can enable/use, such as generating a draft which you can manually publish yourself from the Play store console.
You can find more details <a href="https://github.com/Triple-T/gradle-play-publisher">here</a>, in the GPPs very well documented README.</p>
<h2 id="meta-data">Meta Data</h2>
<p>You can also use the GPP to publish meta-data about our app such as the listing, photos etc. To do this you can either
run <code>cd android &amp;&amp; ./gradlew bootstrap</code> which will initialise the directory layout you need and also pull the
information from the play store if you already have it. If you do not want to run this command, you can create a
structure similar to the one shown below in your <code>android/app/src/main/play/</code> folder. You can find more information
about meta-data <a href="https://github.com/Triple-T/gradle-play-publisher#managing-play-store-metadata">here</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">├── contact-email.txt
</span></span><span class="line"><span class="cl">├── contact-website.txt
</span></span><span class="line"><span class="cl">├── default-language.txt
</span></span><span class="line"><span class="cl">├── listings
</span></span><span class="line"><span class="cl">│   └── en-GB
</span></span><span class="line"><span class="cl">│       ├── full-description.txt
</span></span><span class="line"><span class="cl">│       ├── graphics
</span></span><span class="line"><span class="cl">│       │   ├── feature-graphic
</span></span><span class="line"><span class="cl">│       │   │   └── 1.png
</span></span><span class="line"><span class="cl">│       │   ├── icon
</span></span><span class="line"><span class="cl">│       │   │   └── 1.png
</span></span><span class="line"><span class="cl">│       │   └── phone-screenshots
</span></span><span class="line"><span class="cl">│       │       ├── 1.png
</span></span><span class="line"><span class="cl">│       │       ├── 2.png
</span></span><span class="line"><span class="cl">│       │       ├── 3.png
</span></span><span class="line"><span class="cl">│       │       ├── 4.png
</span></span><span class="line"><span class="cl">│       │       ├── 5.png
</span></span><span class="line"><span class="cl">│       │       └── 6.png
</span></span><span class="line"><span class="cl">│       ├── short-description.txt
</span></span><span class="line"><span class="cl">│       ├── title.txt
</span></span><span class="line"><span class="cl">│       └── video-url.txt
</span></span><span class="line"><span class="cl">├── release-names
</span></span><span class="line"><span class="cl">│   └── default.txt
</span></span><span class="line"><span class="cl">└── release-notes
</span></span><span class="line"><span class="cl">    └── en-GB
</span></span><span class="line"><span class="cl">        └── default.txt
</span></span></code></pre></div><h2 id="packagejson">package.json</h2>
<p>Add the following script to your <code>package.json</code>, this will be used within our <code>.gitlab-ci.yml</code>. This is so that we can
simply use yarn run bundle, for example instead of having to write the whole command, in our GitLab CI. Also, the other
advantage is that the command is used multiple times in our GitLab CI jobs, we only have to edit in a single location. It also
saves us having to type out the same (very long) command again.</p>
<ul>
<li>bundle: Bundles all of our react native code into a single file</li>
<li>publish-package: Will build our AAB and also publish it along with all the meta-data we include</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="nt">&#34;scripts&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;bundle&#34;</span><span class="p">:</span> <span class="s2">&#34;react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.bundle --sourcemap-output android/app/src/main/assets/index.map --assets-dest android/app/src/main/res&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;publish-package&#34;</span><span class="p">:</span> <span class="s2">&#34;cd android &amp;&amp; ./gradlew publishRelease&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>  <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span></code></pre></div><h2 id="gitlab-ciyml">.gitlab-ci.yml</h2>
<p>Now finally we can add our job to our GitLab CI file.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">publish:android:package</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">reactnativecommunity/react-native-android</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">echo fs.inotify.max_user_watches=524288 | tee -a /etc/sysctl.conf &amp;&amp; sysctl -p</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">mv $PLAY_STORE_JSON android/app/play-store.json</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn bundle</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn publish-package --no-daemon</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">artifacts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./android/app/build/outputs/</span><span class="w">
</span></span></span></code></pre></div><p>Let&rsquo;s break this file down line by line;
We need a Docker image which contains all the prerequisites for building our APK. A good one
to use is the <a href="https://hub.docker.com/r/reactnativecommunity/react-native-android">reactnativecommunity/react-native-android</a>.
It has everything we need for React Native/Android (Java, Android SDK etc).</p>
<p>Depending on your exact project, you may need to increase the inoitfy file watcher limit. You can do this by using
<code>echo fs.inotify.max_user_watches=524288 | tee -a /etc/sysctl.conf &amp;&amp; sysctl -p</code>. Essentially file watchers are used
to monitor changes in the file system. You can find more information about
Linux&rsquo;s inotify <a href="https://github.com/guard/listen/wiki/Increasing-the-amount-of-inotify-watchers">here</a>.</p>
<p><code>yarn bundle</code>: We create a bundle this is where all of our (JavaScript) React Native file are bundled into a single Javascript
file.</p>
<p><code>yarn android-publish --no-daemon</code>: This builds our AAB locally and then publish it to the <code>Android Play Store</code>, to the internal testing track in this case.
It will also publish all of our meta-data for us such as the photos, store listing, contact information and release notes (etc).
Since this is a CI job we don&rsquo;t need to start a daemon, to speed up future builds, hence the <code>--no-daemon</code> argument.</p>
<p>Finally we create artifacts <code>./android/app/build/outputs/</code> so that we can download the AAB after the job has been completed. I also like to include
my artifacts (AAB) with releases of my apps within GitLab. It makes it easier to track what version of the app was released when. But if you don&rsquo;t
want the AAB/APK you can simply remove the artifacts part from the job.</p>
<p>That&rsquo;s it, we&rsquo;re done :).</p>
<p>
  <img
    loading="lazy"
    src="https://giphy.com/gifs/13Csc1WMn9sXSM/html5"
    alt="More automation is good right?"
    
  /></p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/stegappasaurus/tree/cde1afd6fbb9d882bccb9e05693824587ce1b77e">Example project</a></li>
<li><a href="https://github.com/Triple-T/gradle-play-publisher">Gradle Play Publisher</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Using Gitlab CI to Publish an Android React Native App</title>
      <link>https://haseebmajid.dev/posts/2019-09-23-using-gitlab-ci-to-publish-an-android-react-native-app/</link>
      <pubDate>Mon, 23 Sep 2019 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2019-09-23-using-gitlab-ci-to-publish-an-android-react-native-app/</guid>
      <description>&lt;p&gt;In this article I will show how you can use the GitLab CI with React Native to create a binary which can be published to the
Google Play Store.&lt;/p&gt;
&lt;h2 id=&#34;prerequisites&#34;&gt;Prerequisites&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://play.google.com/apps/publish/signup/#EnterDetailsPlace&#34;&gt;Google Developers Account&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;A working React Native Android project&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;keystore&#34;&gt;Keystore&lt;/h2&gt;
&lt;p&gt;First, we have to generate a keystore which we will use to sign our APK. To do this run the commands below,
follow all the instructions and keep the file safe.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this article I will show how you can use the GitLab CI with React Native to create a binary which can be published to the
Google Play Store.</p>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li><a href="https://play.google.com/apps/publish/signup/#EnterDetailsPlace">Google Developers Account</a></li>
<li>A working React Native Android project</li>
</ul>
<hr>
<h2 id="keystore">Keystore</h2>
<p>First, we have to generate a keystore which we will use to sign our APK. To do this run the commands below,
follow all the instructions and keep the file safe.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Used to generate our keystore</span>
</span></span><span class="line"><span class="cl">keytool -genkeypair -v -keystore my-key.keystore -alias my-key-alias -keyalg RSA -keysize <span class="m">2048</span> -validity <span class="m">10000</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Used to encode our keystore in base64</span>
</span></span><span class="line"><span class="cl">base64 my-key.keystore &gt; base64-keystore.txt
</span></span></code></pre></div><h3 id="app-signing">App signing</h3>
<p>We no longer need to upload a certificate manually, as long as you &ldquo;Opt In&rdquo;, to let Google Sign your applications for you.
It will keep track of the key you used to upload your very first APK file, then it will expect you to sign all new releases
of that application using that same key, so don&rsquo;t lose your keystore. To allow Google to sign our app for us do the following;</p>
<ul>
<li>Login to <a href="https://play.google.com/apps/publish">Google Play Console</a></li>
<li>Select your application from the list</li>
<li>Select &ldquo;App Releases&rdquo;</li>
<li>Select a track, for example &ldquo;Internal test&rdquo; &gt; &ldquo;Manage&rdquo;</li>
<li>Select &ldquo;Create Release&rdquo;</li>
<li>Then where it says <code>Let Google manage and protect your app signing key (recommended)</code>, Select &ldquo;Continue&rdquo;</li>
</ul>
<p>The signing process works as follows;</p>
<ul>
<li>You digitally sign each release using your upload key (upload key being the keystore we just generated) before publishing it on the Play Console.</li>
<li>Google Play uses the upload certificate to verify your identity and then re-signs your release using the app signing key for distribution.</li>
</ul>
<h3 id="gitlab">GitLab</h3>
<p>Now, let&rsquo;s move on to the relevant keystore information to GitLab CI variables, so we can access them during our CI jobs.
First, go to your GitLab project;</p>
<ul>
<li>Settings (side menu) &gt; CI/CD &gt; Variables</li>
<li>Add Type: Variable, key: <code>ANDROID_KEYSTORE_ALIAS</code> , value: <code>my-key-alias</code></li>
<li>Add Type: Variable, key: <code>ANDROID_KEYSTORE_PASSWORD</code>, value: (whatever password you used)</li>
<li>Add Type: Variable, key: <code>ANDROID_KEYSTORE_KEY_PASSWORD</code>, value: (whatever password you used, by default it&rsquo;s the same as the <code>ANDROID_KEYSTORE_PASSWORD</code>)</li>
<li>Add Type: <code>File</code>, key: <code>ANDROID_KEYSTORE</code>, value: (copy the contents of <code>base64-keystore.txt</code>)</li>
</ul>
<p>Now we have all our keystore values/files on GitLab CI.</p>
<p><strong>Note</strong> Check the project permissions so only the relevant users can see/edit these values.
You should keep the keystore file/passwords private, make sure only the relevant users can access them.</p>
<hr>
<h2 id="appbuildgradle">app/build.gradle</h2>
<p>Next open the <code>android/app/build.gradle</code>, then add the following to the <code>android{}</code> section.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-groovy" data-lang="groovy"><span class="line"><span class="cl"><span class="n">android</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">signingConfigs</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">release</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="o">(</span><span class="n">project</span><span class="o">.</span><span class="na">hasProperty</span><span class="o">(</span><span class="s1">&#39;MYAPP_RELEASE_STORE_FILE&#39;</span><span class="o">))</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">storeFile</span> <span class="nf">file</span><span class="o">(</span><span class="n">MYAPP_RELEASE_STORE_FILE</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">                <span class="n">storePassword</span> <span class="n">MYAPP_RELEASE_STORE_PASSWORD</span>
</span></span><span class="line"><span class="cl">                <span class="n">keyAlias</span> <span class="n">MYAPP_RELEASE_KEY_ALIAS</span>
</span></span><span class="line"><span class="cl">                <span class="n">keyPassword</span> <span class="n">MYAPP_RELEASE_KEY_PASSWORD</span>
</span></span><span class="line"><span class="cl">            <span class="o">}</span>
</span></span><span class="line"><span class="cl">        <span class="o">}</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">buildTypes</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">release</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">minifyEnabled</span> <span class="n">enableProguardInReleaseBuilds</span>
</span></span><span class="line"><span class="cl">            <span class="n">proguardFiles</span> <span class="nf">getDefaultProguardFile</span><span class="o">(</span><span class="s2">&#34;proguard-android.txt&#34;</span><span class="o">),</span> <span class="s2">&#34;proguard-rules.pro&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="n">signingConfig</span> <span class="n">signingConfigs</span><span class="o">.</span><span class="na">release</span>
</span></span><span class="line"><span class="cl">        <span class="o">}</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></div><p>All this does is generate a signed APK so we can upload it to the Android Play Store. It will use the
values from the keystore we just generated, to sign our application. We will create a <code>gradle.propeties</code>
file so we don&rsquo;t have to store our keystore values in plain-text within the <code>app/build.gradle</code>
(The <code>gradle.properties</code> file will be generated during our CI job, using the values we stored earlier on GitLab)</p>
<p><strong>Note</strong>: I have added my <code>gradle.properties</code> file to my <code>.gitignore</code> file so it doesn&rsquo;t accidentally get committed
when I am testing out the build process locally. I recommend you do the same.</p>
<hr>
<h2 id="packagejson">package.json</h2>
<p>Add the following three scripts to your <code>package.json</code> file. This is so that we can simply use <code>yarn run bundle</code>
for example instead of having to write out the whole command, in our GitLab CI. Also, the other advantage is
if the command is used multiple times in our GitLab CI jobs, we only have to edit in a single place.</p>
<ul>
<li>build-package: Builds our APK file</li>
<li>bundle: Bundles all of our react native code into a single file</li>
<li>generate-gradle-properties: Creates a gradle.properties file for us in the <code>android folder</code>.</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;scripts&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;android-package&#34;</span><span class="p">:</span> <span class="s2">&#34;cd android &amp;&amp; ./gradlew assembleRelease&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;bundle&#34;</span><span class="p">:</span> <span class="s2">&#34;react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.bundle --sourcemap-output android/app/src/main/assets/index.map --assets-dest android/app/src/main/res&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;generate-gradle-properties&#34;</span><span class="p">:</span> <span class="s2">&#34;sh generate-gradle-properties.sh &gt; android/gradle.properties&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="err">...</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="err">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Where the <code>generate-gradle-propeties.sh</code> file looks something like, the code below.
The file is essentially a template file, where the <code>${variable}</code> are determined by
the environment variables set (the values we set earlier on GitLab). So, in this case,
the GitLab CI will pass in our keystore variables as environment variables and this
file will simply fill them in and will create our <code>gradle.properties</code> file.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env bash
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl">cat  <span class="s">&lt;&lt; EOF
</span></span></span><span class="line"><span class="cl"><span class="s">android.useAndroidX=true
</span></span></span><span class="line"><span class="cl"><span class="s">android.enableJetifier=true
</span></span></span><span class="line"><span class="cl"><span class="s">MYAPP_RELEASE_STORE_FILE=my-key.keystore
</span></span></span><span class="line"><span class="cl"><span class="s">MYAPP_RELEASE_STORE_PASSWORD=${ANDROID_KEYSTORE_PASSWORD}
</span></span></span><span class="line"><span class="cl"><span class="s">MYAPP_RELEASE_KEY_ALIAS=${ANDROID_KEYSTORE_ALIAS}
</span></span></span><span class="line"><span class="cl"><span class="s">MYAPP_RELEASE_KEY_PASSWORD=${ANDROID_KEYSTORE_KEY_PASSWORD}
</span></span></span><span class="line"><span class="cl"><span class="s">EOF</span>
</span></span></code></pre></div><hr>
<h2 id="gitlab-ciyml">.gitlab-ci.yml</h2>
<p>Add the following job to your <code>.gitlab-ci.yml</code> file, this job will create our APK.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">build</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">build:android:package</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">build</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">reactnativecommunity/react-native-android</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">echo fs.inotify.max_user_watches=524288 | tee -a /etc/sysctl.conf &amp;&amp; sysctl -p</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn install</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">base64 -d $ANDROID_KEYSTORE &gt; android/app/my-key.keystore</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn generate-gradle-properties</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn bundle</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn android-package --no-daemon</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">artifacts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./android/app/build/outputs/</span><span class="w">
</span></span></span></code></pre></div><p>Let&rsquo;s break this job down line by line;
First we need a Docker image which contains all the prerequisites for building our APK. I think the
<a href="https://hub.docker.com/r/reactnativecommunity/react-native-android">reactnativecommunity/react-native-android</a>
has everything we need for our React Native/Android build (Java, Android SDK etc).</p>
<p>Depending on your exact project, you may need to increase the <code>inoitfy file watcher</code> limit, you can do this using
<code>echo fs.inotify.max_user_watches=524288 | tee -a /etc/sysctl.conf &amp;&amp; sysctl -p</code>. Essentially, file watchers are used
to monitor changes in the file system. You can find more information about
Linux&rsquo;s inotify <a href="https://github.com/guard/listen/wiki/Increasing-the-amount-of-inotify-watchers">here</a>.</p>
<p>We then install our project dependencies using <code>yarn install</code>.</p>
<p>We then decode the keystore file <code>base64 -d $ANDROID_KEYSTORE &gt; android/app/my-key.keystore</code> the file need to be saved in
<code>android/app</code> folder so it can be used during the building of the APK.</p>
<p>We then generate our <code>gradle.properties</code> file using <code>yarn generate-gradle-properties</code>, where we store variables required during the
build process such as the keystore passsword.</p>
<p>Then we run <code>yarn bundle</code>, which creates the bundle, where all of our (JavaScript) React Native file are bundled into a single Javascript
file.</p>
<p>Finally we run the command that will actually build our APK <code>yarn android-build-apk --no-daemon</code>. Since this is a <code>CI</code> job we
don&rsquo;t need to start a daemon, to speed up future builds hence the <code>--no-daemon</code> argument.</p>
<p>Also we make some build artifacts available so everything with in this folder, <code>./android/app/build/outputs/</code> can be accessed/downloaded
the APK after the job has completed, so we can then upload our APK manually.</p>
<h3 id="aab">AAB</h3>
<p>An AAB is the <a href="https://developer.android.com/guide/app-bundle">Android App Bundle</a>, which is now the recommended
way to upload our app to the Play Store. It has a few advantages over the APK, the main one being it usually
makes your app slimmer and takes fewer bytes on your users device&rsquo;s. Luckily for us the change in code required to
create an AAB instead of an AAB is very small.</p>
<p>All we have to do is open our <code>package.json</code> and edit the <code>android-package</code> script so that it contains the following
<code>&quot;android-package&quot;: &quot;cd android &amp;&amp; ./gradlew bundleRelease&quot;</code>. So essentially we change the Gradle target from
<code>assembleRelease</code> to <code>bundleRelease</code> and that it.</p>
<p><img
        loading="lazy"
        src="/posts/2019-09-23-using-gitlab-ci-to-publish-an-android-react-native-app/images/gitlab.gif"
        type=""
        alt="Example GitLab CI job"
        
      /></p>
<hr>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/stegappasaurus/blob/4eb78b18e5677b9f6956750d50ea50725c4d099f">Example project</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Creating a Simple RESTful App using OpenAPI, Flask &amp; Connexions</title>
      <link>https://haseebmajid.dev/posts/2019-08-16-creating-a-simple-restful-app-using-openapi-flask-connexions/</link>
      <pubDate>Fri, 16 Aug 2019 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2019-08-16-creating-a-simple-restful-app-using-openapi-flask-connexions/</guid>
      <description>&lt;p&gt;RESTful APIs are very popular at the moment and Python is a great language to develop
web APIs with. In this article we will go over a documentation first approach to building APIs.
We will be using Flask, Swagger Code-Gen (OpenAPI) and Connexions.
I will go over an API/documentation first approach to building a RESTful API in
Python. Which will try to minimise the differences between what&amp;rsquo;s defined in the API
specification and the actual API logic
itself.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>RESTful APIs are very popular at the moment and Python is a great language to develop
web APIs with. In this article we will go over a documentation first approach to building APIs.
We will be using Flask, Swagger Code-Gen (OpenAPI) and Connexions.
I will go over an API/documentation first approach to building a RESTful API in
Python. Which will try to minimise the differences between what&rsquo;s defined in the API
specification and the actual API logic
itself.</p>
<p>One of the main problems you&rsquo;ll find with using openapi is that every time you update your API
you have to update your documentation or your openapi yaml/json file. Now what happens if you
forget? Now your API is different to what&rsquo;s documented which can be a real pain for your users.
The aim of this approach is that you update your specification file first.</p>
<h2 id="toolslibraries">Tools/Libraries</h2>
<p>Let&rsquo;s very quickly go over the tools and libraries we will use.</p>
<h3 id="openapi">OpenAPI</h3>
<p>Openapi or the Openapi Specification (OAS), defines a standard language agnostic approach to
developing RESTful APIs, which are both human and machine readable.</p>
<h3 id="swagger">Swagger</h3>
<p>A set of open-source tools built around the OAS that help support development, including:</p>
<ul>
<li>Swagger Editor: Browser based editor where you can write (and view) OpenAPI specs.</li>
<li>Swagger UI: Renders OAS as interactive API documentation (also can be seen within Swagger Editor).</li>
<li>Swagger Codegen - generates server stubs and client libraries from an OpenAPI spec.</li>
</ul>
<h3 id="connexion">Connexion</h3>
<p>Is a Python library that &ldquo;automagically&rdquo; handles HTTP requests based on your OAS. It acts as a
simple wrapper around Flask reducing the boilerplate code you have to write as well. So we still
have access to all the functionality we would have when developing a normal Flask web API.</p>
<p><strong>NOTE:</strong> At the time of writing this article OAS3 support had just come out for codegen.
So this article is written using OAS2. However everything in this article should be applicable
to OAS2 and AOS3.</p>
<p>
  <img
    loading="lazy"
    src="https://synaptiklabs.com/wp-content/uploads/2019/02/javaee-swagger-screen-1.png"
    alt="Swagger UI"
    
  /></p>
<h2 id="api">API</h2>
<p>Now onto actually developing our API.</p>
<h3 id="project-structure">Project Structure</h3>
<p>In this article our code will be using the following structure.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">test-api/
</span></span><span class="line"><span class="cl">├── openapi/
</span></span><span class="line"><span class="cl">├── src/
</span></span><span class="line"><span class="cl">|   └── test_api
</span></span><span class="line"><span class="cl">|   |  ├── wsgi.py
</span></span><span class="line"><span class="cl">|   |  ├──__init__.py
</span></span><span class="line"><span class="cl">|   |  ├── core/
</span></span><span class="line"><span class="cl">|   |  └── web/
</span></span><span class="line"><span class="cl">└── setup.py
</span></span></code></pre></div><hr>
<h3 id="define-specification">Define Specification</h3>
<p>First thing we do is define our OAS. We will use YAML to do this because I think it&rsquo;s much easier to read
and almost all specifications you see will be written in YAML (not JSON). However you can write the
specification in JSON if you so wish. There are a few tools that can make this a bit easier.</p>
<p>We can use the <a href="https://editor.swagger.io/">online swagger editor</a>, which allows us to edit the
OAS and you can see the OAS as an interactive document (half the screen for the editor and half
for the interactive document). You can also run the editor locally as a
<a href="https://hub.docker.com/r/swaggerapi/swagger-editor/">Docker container</a></p>
<p><strong>NOTE</strong>: If you use the editor to generate models (using swagger-codegn), it makes an API call to
a remote server. Run the swagger-codegen manually to generate the models locally, if you&rsquo;re using
this for work and confidentality matters.</p>
<p>My preferred way of writing an OAS is using VSCode with the
<a href="https://marketplace.visualstudio.com/items?itemName=Arjun.swagger-viewer">Swagger Viewer</a>
plugin, which allows you to write the OAS and preview the interactive document at the same time.
I prefer this approach because I have all my plugins setup (colour scheme, vim bindings etc).</p>
<p>Now we have to define our specification. We will be using OAS version 2 because swagger-codegen
at the moment cannot generate models for flask for OAS version 3. Now I&rsquo;ve created a very simple
specification for an imaginary pet store.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">swagger</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;2.0&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">info</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;1.0.0&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Pet Store&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">basePath</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;/api/v1&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">tags</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;pet&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">schemes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="s2">&#34;https&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">consumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="s2">&#34;application/json&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">produces</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="s2">&#34;application/json&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="l">/pet/{pet_id}:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">get</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">tags</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="s2">&#34;pet&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">summary</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Get a pet in the store&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">operationId</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;get_pet&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">parameters</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;pet_id&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">in</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;path&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;The id of the pet to retrieve&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">required</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;string&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">responses</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">200</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Successfully retrived pet&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">schema</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">$ref</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#/definitions/Pet&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">404</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Pet doesn&#39;t exist&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">x-swagger-router-controller</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;test_api.web.controllers.pets_controller&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">delete</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">tags</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="s2">&#34;pet&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">summary</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Remove a pet in the store&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">operationId</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;remove_pet&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">parameters</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;pet_id&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">in</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;path&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;The id of the pet to remove from the store&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">required</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;string&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">responses</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">202</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Successfully deleted pet&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">404</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Pet doesn&#39;t exist&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">x-swagger-router-controller</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;test_api.web.controllers.pets_controller&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">put</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">tags</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="s2">&#34;pet&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">summary</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Update and replace a pet in the store&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">operationId</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;update_pet&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">parameters</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;pet_id&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">in</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;path&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;The id of the pet to update from the store&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">required</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;string&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Pet&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">in</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;body&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">required</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">schema</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">$ref</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#/definitions/Pet&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">responses</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">200</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Successfully updated pet&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">404</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Pet doesn&#39;t exist&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">x-swagger-router-controller</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;test_api.web.controllers.pets_controller&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">/pet</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">get</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">tags</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="s2">&#34;pet&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">summary</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Gets all pets in the store&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">operationId</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;get_all_pets&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">responses</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">200</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Successfully received all pets.&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">schema</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">$ref</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#/definitions/Pets&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">x-swagger-router-controller</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;test_api.web.controllers.pets_controller&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">post</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">tags</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="s2">&#34;pet&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">summary</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Add a new pet to the store&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">operationId</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;add_pet&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">parameters</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="nt">in</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;body&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;body&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Pet to add to the store&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">required</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">schema</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">$ref</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#/definitions/Pet&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">responses</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">201</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Pet added&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">x-swagger-router-controller</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;test_api.web.controllers.pets_controller&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">definitions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">Pets</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">array</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">items</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">$ref</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#/definitions/Pet&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">Pet</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;object&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">required</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;name&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;breed&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;price&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">properties</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">id</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;integer&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">format</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;int32&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">name</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;string&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">breed</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;string&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">price</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;number&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">format</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;float&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">example</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;doggie&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">breed</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;German Shepherd&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">price</span><span class="p">:</span><span class="w"> </span><span class="m">100.00</span><span class="w">
</span></span></span></code></pre></div><p>The specification defines several endpoints for our API. Essentially I&rsquo;ve defined one endpoint
for each of the main CRUD verbs (GET, POST, PUT and DELETE). Some things to note:
the <code>operation_id</code>, will be the function name in our Python code. In a production,
you should also look at using <code>OAuth2</code> for securing your API this can also be defined within in
the specification.</p>
<p><strong>Note</strong> the extra field <code>x-swagger-router-controller</code> is very important. It is used by <code>Connexion</code> to
map which module (and function) to send requests to. For example a GET request send to <code>/api/v1/pets</code>,
will go to <code>test_api.web.controllers.pets_controller</code> and function called <code>get_pet</code> (<code>operation_id</code>)
so it looks like <code>test_api.web.controllers.pets_controller:get_pet</code>. Which means we call the function
in the folder <code>src/test_api/web/controllers/pets_controller</code> we call the <code>get_pet</code> function.</p>
<h3 id="server-stubs">Server Stubs</h3>
<p>Now we want to generate some server stubs from this specification we can do this by either using the <code>codegen</code> tool or
in the <code>editor</code> we can go to <code>Generate Server &gt; python-flask</code>. This will download a zip file, after you decompress it.
We want to copy the <code>controllers, models, encoder.py, __init__.py and util.py</code> files into the <code>web</code> folder. The models
are the classes of objects that we expect as input and output such as a <code>Pet</code> class. The controllers contain the actual
webserver logic. There is one function for every endpoint (and CRUD method) we defined above, there is also one file for
every tag we defined. In this example we only have one controller file because we only have <code>tag</code> called pet. Then in the
controller we have 4 functions (named after the <code>operation_id</code>).</p>
<p>We have to make some changes to the codegen generated files. The imports will be wrong when we move the files. We have to change them from
<code>swagger_server</code>. So for example <code>controllers/pet_controller.py</code> and <code>models/pets.py</code> would become:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">connexion</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">six</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">..models.pet</span> <span class="kn">import</span> <span class="n">Pet</span>  <span class="c1"># noqa: E501</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">..models.pets</span> <span class="kn">import</span> <span class="n">Pets</span>  <span class="c1"># noqa: E501</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">..</span> <span class="kn">import</span> <span class="n">util</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">test_api.core</span> <span class="kn">import</span> <span class="n">pets</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">add_pet</span><span class="p">(</span><span class="n">body</span><span class="p">):</span>  <span class="c1"># noqa: E501</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Add a new pet to the store
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">     # noqa: E501
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">    :param body: Pet to add to the store
</span></span></span><span class="line"><span class="cl"><span class="s2">    :type body: dict | bytes
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">    :rtype: None
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">connexion</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">is_json</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">body</span> <span class="o">=</span> <span class="n">Pet</span><span class="o">.</span><span class="n">from_dict</span><span class="p">(</span><span class="n">connexion</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">get_json</span><span class="p">())</span>  <span class="c1"># noqa: E501</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="n">pets</span><span class="o">.</span><span class="n">add_pet</span><span class="p">(</span><span class="n">body</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">{},</span> <span class="mi">201</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_all_pets</span><span class="p">():</span>  <span class="c1"># noqa: E501</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Gets all pets in the store
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">     # noqa: E501
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">    :rtype: Pets
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">pets</span> <span class="o">=</span> <span class="n">pets</span><span class="o">.</span><span class="n">get_all_pets</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">pets_in_store</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">pet</span> <span class="ow">in</span> <span class="n">pets</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">current_pet</span> <span class="o">=</span> <span class="n">Pet</span><span class="p">(</span><span class="nb">id</span><span class="o">=</span><span class="n">pet</span><span class="p">[</span><span class="s2">&#34;id&#34;</span><span class="p">],</span> <span class="n">breed</span><span class="o">=</span><span class="n">pet</span><span class="p">[</span><span class="s2">&#34;breed&#34;</span><span class="p">],</span> <span class="n">name</span><span class="o">=</span><span class="n">pet</span><span class="p">[</span><span class="s2">&#34;name&#34;</span><span class="p">],</span> <span class="n">price</span><span class="o">=</span><span class="n">pet</span><span class="p">[</span><span class="s2">&#34;price&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">        <span class="n">pets_in_store</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">current_pet</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">pets_in_store</span><span class="p">,</span> <span class="mi">200</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_pet</span><span class="p">(</span><span class="n">pet_id</span><span class="p">):</span>  <span class="c1"># noqa: E501</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Get a pet in the store
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">     # noqa: E501
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">    :param pet_id: The id of the pet to retrieve
</span></span></span><span class="line"><span class="cl"><span class="s2">    :type pet_id: str
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">    :rtype: Pet
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">pet</span> <span class="o">=</span> <span class="n">pets</span><span class="o">.</span><span class="n">get_pet</span><span class="p">(</span><span class="n">pet_id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">response</span> <span class="o">=</span> <span class="n">Pet</span><span class="p">(</span><span class="nb">id</span><span class="o">=</span><span class="n">pet</span><span class="o">.</span><span class="n">id</span><span class="p">,</span> <span class="n">breed</span><span class="o">=</span><span class="n">pet</span><span class="o">.</span><span class="n">breed</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="n">pet</span><span class="o">.</span><span class="n">name</span><span class="p">,</span> <span class="n">price</span><span class="o">=</span><span class="n">pet</span><span class="o">.</span><span class="n">price</span><span class="p">),</span> <span class="mi">200</span>
</span></span><span class="line"><span class="cl">    <span class="k">except</span> <span class="ne">KeyError</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">response</span> <span class="o">=</span> <span class="p">{},</span> <span class="mi">404</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">response</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">remove_pet</span><span class="p">(</span><span class="n">pet_id</span><span class="p">):</span>  <span class="c1"># noqa: E501</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Remove a pet in the store
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">     # noqa: E501
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">    :param pet_id: The id of the pet to remove from the store
</span></span></span><span class="line"><span class="cl"><span class="s2">    :type pet_id: str
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">    :rtype: None
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">pets</span><span class="o">.</span><span class="n">remove_pet</span><span class="p">(</span><span class="n">pet_id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">response</span> <span class="o">=</span> <span class="p">{},</span> <span class="mi">200</span>
</span></span><span class="line"><span class="cl">    <span class="k">except</span> <span class="ne">KeyError</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">response</span> <span class="o">=</span> <span class="p">{},</span> <span class="mi">404</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">response</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">update_pet</span><span class="p">(</span><span class="n">pet_id</span><span class="p">,</span> <span class="n">Pet</span><span class="p">):</span>  <span class="c1"># noqa: E501</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Update and replace a pet in the store
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">     # noqa: E501
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">    :param pet_id: The id of the pet to update from the store
</span></span></span><span class="line"><span class="cl"><span class="s2">    :type pet_id: str
</span></span></span><span class="line"><span class="cl"><span class="s2">    :param Pet: 
</span></span></span><span class="line"><span class="cl"><span class="s2">    :type Pet: dict | bytes
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">    :rtype: None
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">connexion</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">is_json</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">Pet</span> <span class="o">=</span> <span class="n">Pet</span><span class="o">.</span><span class="n">from_dict</span><span class="p">(</span><span class="n">connexion</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">get_json</span><span class="p">())</span>  <span class="c1"># noqa: E501</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">pets</span><span class="o">.</span><span class="n">update_pet</span><span class="p">(</span><span class="n">pet_id</span><span class="p">,</span> <span class="n">Pet</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">response</span> <span class="o">=</span> <span class="p">{},</span> <span class="mi">200</span>
</span></span><span class="line"><span class="cl">    <span class="k">except</span> <span class="ne">KeyError</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">response</span> <span class="o">=</span> <span class="p">{},</span> <span class="mi">404</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">response</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">json</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_all_pets</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">pets</span> <span class="o">=</span> <span class="n">read_from_file</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">pets_in_store</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">k</span><span class="p">,</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">pets</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">current_pet</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;id&#34;</span><span class="p">:</span> <span class="n">k</span><span class="p">,</span> <span class="o">**</span><span class="n">v</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="n">pets_in_store</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">current_pet</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">pets</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">remove_pet</span><span class="p">(</span><span class="nb">id</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">pets</span> <span class="o">=</span> <span class="n">read_from_file</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">del</span> <span class="n">pets</span><span class="p">[</span><span class="nb">id</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">write_to_file</span><span class="p">(</span><span class="n">pets</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">update_pet</span><span class="p">(</span><span class="nb">id</span><span class="p">,</span> <span class="n">pet</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">pets</span> <span class="o">=</span> <span class="n">read_from_file</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">ids</span> <span class="o">=</span> <span class="n">pets</span><span class="o">.</span><span class="n">keys</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">pets</span><span class="p">[</span><span class="nb">id</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="n">pet</span><span class="o">.</span><span class="n">name</span><span class="p">,</span> <span class="s2">&#34;breed&#34;</span><span class="p">:</span> <span class="n">pet</span><span class="o">.</span><span class="n">breed</span><span class="p">,</span> <span class="s2">&#34;price&#34;</span><span class="p">:</span> <span class="n">pet</span><span class="o">.</span><span class="n">price</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">write_to_file</span><span class="p">(</span><span class="n">pets</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">add_pet</span><span class="p">(</span><span class="n">pet</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">pets</span> <span class="o">=</span> <span class="n">read_from_file</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">ids</span> <span class="o">=</span> <span class="n">pets</span><span class="o">.</span><span class="n">keys</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">new_id</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">ids</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">])</span> <span class="o">+</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">    <span class="n">pets</span><span class="p">[</span><span class="n">new_id</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="n">pet</span><span class="o">.</span><span class="n">name</span><span class="p">,</span> <span class="s2">&#34;breed&#34;</span><span class="p">:</span> <span class="n">pet</span><span class="o">.</span><span class="n">breed</span><span class="p">,</span> <span class="s2">&#34;price&#34;</span><span class="p">:</span> <span class="n">pet</span><span class="o">.</span><span class="n">price</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">write_to_file</span><span class="p">(</span><span class="n">pets</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_pet</span><span class="p">(</span><span class="nb">id</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">pets</span> <span class="o">=</span> <span class="n">read_from_file</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">pet</span> <span class="o">=</span> <span class="n">pets</span><span class="p">[</span><span class="nb">id</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">pet</span><span class="p">[</span><span class="s2">&#34;id&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="nb">id</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">pet</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">write_to_file</span><span class="p">(</span><span class="n">content</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s2">&#34;./pets.json&#34;</span><span class="p">,</span> <span class="s2">&#34;w&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">pets</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">pets</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">content</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">read_from_file</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s2">&#34;./pets.json&#34;</span><span class="p">,</span> <span class="s2">&#34;r&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">pets</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">pets</span><span class="o">.</span><span class="n">read</span><span class="p">())</span>
</span></span></code></pre></div><p>In this case I&rsquo;m using relative imports but you could also use absolute imports. For example <code>..models.patch_request</code> would
become <code>test_api.models.patch_request</code>. It&rsquo;s all personal preference. <a href="https://realpython.com/absolute-vs-relative-python-imports/">This article</a>
goes into more detail on the issue.</p>
<p><strong>Note:</strong> Some imports aren&rsquo;t required and can always be removed later, this will vary project to project. You can use a linter to help you determine
unused imports.</p>
<p>So now we have generated some models and controllers from our openapi specification we can write the logic for our application. I usually
write all of my core logic in a folder called <code>core</code> which is a sibling of <code>test_api</code>. Then I import the modules into the controllers. This
adds a nice layer of abstraction, let&rsquo;s say tomorrow you wanted to turn into a cli we can keep the <code>core</code> folder and delete the <code>web</code> folder
and add a cli library such as <code>click</code>. This involves minimal code change.</p>
<p><strong>Note</strong> Some import maybe unnecessary you can use a linter (such as <code>flask8</code>) to help you remove them from the <code>models</code>.</p>
<h3 id="core-logic">Core Logic</h3>
<p>I&rsquo;ve created a file called <code>pets.py</code> in <code>core</code>. In this example we just write and read from a JSON
file. This isn&rsquo;t the best code I&rsquo;ve written but should be enough to show what we&rsquo;re trying to achieve.
In reality this data would likely be stored in a database but I don&rsquo;t want to overcomplicate this
example. As far as you&rsquo;re concerned data is being stored and retrieve from a file as if it were a
database.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">add_pet</span><span class="p">(</span><span class="n">pet</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">pets</span> <span class="o">=</span> <span class="n">read_from_file</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">ids</span> <span class="o">=</span> <span class="n">pets</span><span class="o">.</span><span class="n">keys</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">new_id</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">ids</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">])</span> <span class="o">+</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">    <span class="n">pets</span><span class="p">[</span><span class="n">new_id</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="n">pet</span><span class="o">.</span><span class="n">name</span><span class="p">,</span> <span class="s2">&#34;breed&#34;</span><span class="p">:</span> <span class="n">pet</span><span class="o">.</span><span class="n">breed</span><span class="p">,</span> <span class="s2">&#34;price&#34;</span><span class="p">:</span> <span class="n">pet</span><span class="o">.</span><span class="n">price</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">write_to_file</span><span class="p">(</span><span class="n">pets</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">...</span>
</span></span></code></pre></div><h3 id="controllers">Controllers</h3>
<p>Now we have our core logic, let&rsquo;s looks at how we interact with it in our controllers, first
<code>import test_api.core import pets</code> import our new file into the controllers (<code>pet_controller</code>).</p>
<p>Then let&rsquo;s look at <code>get_pet</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_pet</span><span class="p">(</span><span class="n">pet_id</span><span class="p">):</span>  <span class="c1"># noqa: E501</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Get a pet in the store
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">     # noqa: E501
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">    :param pet_id: The id of the pet to retrieve
</span></span></span><span class="line"><span class="cl"><span class="s2">    :type pet_id: str
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">    :rtype: Pet
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">pet</span> <span class="o">=</span> <span class="n">pets</span><span class="o">.</span><span class="n">get_pet</span><span class="p">(</span><span class="n">pet_id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">response</span> <span class="o">=</span> <span class="n">Pet</span><span class="p">(</span><span class="nb">id</span><span class="o">=</span><span class="n">pet</span><span class="o">.</span><span class="n">id</span><span class="p">,</span> <span class="n">breed</span><span class="o">=</span><span class="n">pet</span><span class="o">.</span><span class="n">breed</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="n">pet</span><span class="o">.</span><span class="n">name</span><span class="p">,</span> <span class="n">price</span><span class="o">=</span><span class="n">pet</span><span class="o">.</span><span class="n">price</span><span class="p">),</span> <span class="mi">200</span>
</span></span><span class="line"><span class="cl">    <span class="k">except</span> <span class="ne">KeyError</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">response</span> <span class="o">=</span> <span class="p">{},</span> <span class="mi">404</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">response</span>
</span></span></code></pre></div><p>As you can see we call our <code>get_pet()</code> function from our <code>core.pets</code> module. Then if the pets exist
we turn the dict that is returned, into a Python object of class <code>Pet</code> as per <code>rtype</code> we defined in
our OAS. Connexion will handle converting this object into JSON. One other thing we do is if a
<code>KeyError</code> exception was thrown, that must mean we don&rsquo;t have a pet with that id in the pet store. Say we have the following</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;1&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;ginger&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;breed&#34;</span><span class="p">:</span> <span class="s2">&#34;bengal&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;price&#34;</span><span class="p">:</span> <span class="mi">100</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;2&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;sam&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;breed&#34;</span><span class="p">:</span> <span class="s2">&#34;husky&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;price&#34;</span><span class="p">:</span> <span class="mi">10</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;3&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;guido&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;breed&#34;</span><span class="p">:</span> <span class="s2">&#34;python&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;price&#34;</span><span class="p">:</span> <span class="mi">518</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>If we try to retrieve a pet of id 4, Python will throw a KeyError saying this doesn&rsquo;t exist (when we load
the JSON file we convert into a dict). So in this case as per our OAS we want to return a 404 pet doesn&rsquo;t
exist.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">responses</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">200</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Successfully retrived pet&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">schema</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">$ref</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#/definitions/Pet&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">404</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Pet doesn&#39;t exist&#34;</span><span class="w">
</span></span></span></code></pre></div><p>One very important thing to note is that when we receive a HTTP request with JSON, say for
the <code>add_pet()</code> function Connexion will convert this into a Python object for us and when we
return a Python object it will convert that Python object into JSON. So within our controllers
and core logic we don&rsquo;t actually need to interact with JSON at all. It&rsquo;s all abstracted away
with the Connexion library. We also don&rsquo;t need to use Swagger codegen to generate the models
and controllers we could&rsquo;ve done ourselves, Connexions can run on it&rsquo;s own without them.</p>
<p>Let&rsquo;s see an example of this.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># pets_controller.py</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">add_pet</span><span class="p">(</span><span class="n">body</span><span class="p">):</span>  <span class="c1"># noqa: E501</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Add a new pet to the store
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">     # noqa: E501
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">    :param body: Pet to add to the store
</span></span></span><span class="line"><span class="cl"><span class="s2">    :type body: dict | bytes
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">    :rtype: None
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">connexion</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">is_json</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">body</span> <span class="o">=</span> <span class="n">Pet</span><span class="o">.</span><span class="n">from_dict</span><span class="p">(</span><span class="n">connexion</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">get_json</span><span class="p">())</span>  <span class="c1"># noqa: E501</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">pets</span><span class="o">.</span><span class="n">add_pet</span><span class="p">(</span><span class="n">body</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">{},</span> <span class="mi">201</span>
</span></span></code></pre></div><p>The body variable will be a Python object of class Pet. We can then pass this as an argument
to our other <code>add_pet</code> function in our core folder. As you can see we access attributes
because it&rsquo;s an object not a dict i.e. <code>pets[&quot;name&quot;]</code> vs <code>pets.name</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># pets.py</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">add_pet</span><span class="p">(</span><span class="n">pet</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">pets</span> <span class="o">=</span> <span class="n">read_from_file</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">ids</span> <span class="o">=</span> <span class="n">pets</span><span class="o">.</span><span class="n">keys</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">new_id</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">ids</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">])</span> <span class="o">+</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">    <span class="n">pets</span><span class="p">[</span><span class="n">new_id</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="n">pet</span><span class="o">.</span><span class="n">name</span><span class="p">,</span> <span class="s2">&#34;breed&#34;</span><span class="p">:</span> <span class="n">pet</span><span class="o">.</span><span class="n">breed</span><span class="p">,</span> <span class="s2">&#34;price&#34;</span><span class="p">:</span> <span class="n">pet</span><span class="o">.</span><span class="n">price</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">write_to_file</span><span class="p">(</span><span class="n">pets</span><span class="p">)</span>
</span></span></code></pre></div><h3 id="swagger-codegen-vs-connexion">Swagger Codegen vs Connexion</h3>
<p>So Connexion does all the routing and validation for us but Swagger codegen is what converts
our input and output into Python classes. Connexions only deals with JSON, it will convert
the JSON into it&rsquo;s equivalent Python object such as lists, strings and dictionary. Swagger
codegen will take this input (a dictionary) and convert that into a Python class.
One example of this in the <code>add_pet</code> function in the <code>pets_controller</code> file. It converts our
dictionary into a <code>Pet</code> object (as shown below). So rather than accessing data using normal
dictionary notation <code>body[&quot;id&quot;]</code> we can now use <code>body.id</code>.
<code>body = Pet.from_dict(connexion.request.get_json()) # noqa: E501</code></p>
<p>For Codegen to convert our Python objects back into a dictionary, so that Connexion can then
convert this into JSON so respond back we use the JSON encoder that codegen provides us
(<code>test_api.web.encoder</code>). To use it all we need to add is to set it as our default encoder
for our flask app <code>flask_app.json_encoder = encoder.JSONEncoder</code>, usually this is done in the
app setup (shown below).</p>
<h2 id="run-a-server">Run a Server</h2>
<p>Now that we have our code how do we actually start up our web application so we can test it. To do this we will create a file which in turn
will create our Connexion/Flask app and start the server, called <code>run.py</code> inside of our <code>test_api</code> folder.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">connexion</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">.web</span> <span class="kn">import</span> <span class="n">encoder</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">create_app</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">abs_file_path</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">abspath</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="vm">__file__</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="n">openapi_path</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">abs_file_path</span><span class="p">,</span> <span class="s2">&#34;../&#34;</span><span class="p">,</span> <span class="s2">&#34;../&#34;</span><span class="p">,</span> <span class="s2">&#34;openapi&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">app</span> <span class="o">=</span> <span class="n">connexion</span><span class="o">.</span><span class="n">FlaskApp</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="vm">__name__</span><span class="p">,</span> <span class="n">specification_dir</span><span class="o">=</span><span class="n">openapi_path</span><span class="p">,</span> <span class="n">options</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;swagger_ui&#34;</span><span class="p">:</span> <span class="kc">False</span><span class="p">,</span> <span class="s2">&#34;serve_spec&#34;</span><span class="p">:</span> <span class="kc">False</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">app</span><span class="o">.</span><span class="n">add_api</span><span class="p">(</span><span class="s2">&#34;specification.yml&#34;</span><span class="p">,</span> <span class="n">strict_validation</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">flask_app</span> <span class="o">=</span> <span class="n">app</span><span class="o">.</span><span class="n">app</span>
</span></span><span class="line"><span class="cl">    <span class="n">flask_app</span><span class="o">.</span><span class="n">json_encoder</span> <span class="o">=</span> <span class="n">encoder</span><span class="o">.</span><span class="n">JSONEncoder</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">flask_app</span>
</span></span></code></pre></div><p>You can run the application like a normal flask app from the project root(running from folder where <code>openapi/</code> and <code>src/</code> exist.)</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">FLASK_APP</span><span class="o">=</span>./src/test_api/run.py <span class="nv">FLASK_DEBUG</span><span class="o">=</span><span class="m">1</span> flask run
</span></span></code></pre></div><h2 id="turn-on-swagger-ui">Turn on Swagger UI</h2>
<p>You can also access Swagger UI from within Connexion. We can access it <code>http://127.0.0.1:5000/api/v1/ui/</code>. To do this we need to update <code>run.py</code> so it looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">connexion</span><span class="o">.</span><span class="n">FlaskApp</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="vm">__name__</span><span class="p">,</span> <span class="n">specification_dir</span><span class="o">=</span><span class="n">openapi_path</span><span class="p">,</span> <span class="n">options</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;swagger_ui&#34;</span><span class="p">:</span> <span class="kc">True</span><span class="p">,</span> <span class="s2">&#34;serve_spec&#34;</span><span class="p">:</span> <span class="kc">True</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span></code></pre></div><h3 id="example-project">Example Project</h3>
<p>Related to this article there is an example project which you can take a look at, to get it running do the following.
Voila we have built a Flask web service with Connexion and OpenAPI.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone https://gitlab.com/hmajid2301/articles.git
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> articles/13.<span class="se">\ </span>REST<span class="se">\ </span>API<span class="se">\ </span>using<span class="se">\ </span>OpenAPI<span class="se">\,\ </span>Flask<span class="se">\ \&amp;\ </span>Connexions/source_code/test-api
</span></span><span class="line"><span class="cl">virtualenv .venv
</span></span><span class="line"><span class="cl"><span class="nb">source</span> .venv/bin/activate
</span></span><span class="line"><span class="cl">pip install -r requirements.txt
</span></span><span class="line"><span class="cl"><span class="nv">FLASK_APP</span><span class="o">=</span>./src/test_api/run.py <span class="nv">FLASK_DEBUG</span><span class="o">=</span><span class="m">1</span> flask run
</span></span></code></pre></div><h2 id="final-thoughts">Final Thoughts</h2>
<p>So as you can see we&rsquo;ve built an web API using Connexion and Flask, where all our code is generated
based of our OAS. So now we are sure our API documentation is accurate. We&rsquo;ve also managed to reduce
some of the boilerplate using Flask, Connexions handles which functions should be called depending on the
CRUD (Create Read Update Delete) operation and endpoints defined in the OAS.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2019-03-04-drawernavigator-tabnavigator-and-stacknavigator-with-react-navigation/source_code">Example source code</a></li>
<li><a href="https://swagger.io/docs/specification/about/">OpenAPI</a></li>
<li><a href="https://github.com/swagger-api/swagger-codegen">Swagger Codegen</a></li>
<li><a href="https://editor.swagger.io/">Swagger Editor</a></li>
<li><a href="https://github.com/zalando/connexion">Connexion</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to add your own type definitions to DefinitelyTyped</title>
      <link>https://haseebmajid.dev/posts/2019-04-19-how-to-add-your-own-type-definitions-to-definitelytyped/</link>
      <pubDate>Fri, 19 Apr 2019 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2019-04-19-how-to-add-your-own-type-definitions-to-definitelytyped/</guid>
      <description>&lt;p&gt;Recently I started using TypeScript (TS) with React Native. Now I won&amp;rsquo;t be going over the benefits of
typescript in this article there are plenty for other articles that will explain the benefits (and drawbacks).&lt;/p&gt;
&lt;p&gt;TS is a superset of JavaScript (JS) so anything JS can do TS can do (and more). One of the main advantages of TS is
it&amp;rsquo;s strict type checking. JS is weakly typed which means variable and parameters can be of any type. One of the
major downsides of this approach is in larger projects it can make code harder to follow and more bug prune. For
example, if you&amp;rsquo;re expecting a variable to be an integer but turns out to be a string. Typescript makes bugs like
this much easier to catch because it is strongly typed and each variable and parameter is given a type.
Lets say you have the following function.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Recently I started using TypeScript (TS) with React Native. Now I won&rsquo;t be going over the benefits of
typescript in this article there are plenty for other articles that will explain the benefits (and drawbacks).</p>
<p>TS is a superset of JavaScript (JS) so anything JS can do TS can do (and more). One of the main advantages of TS is
it&rsquo;s strict type checking. JS is weakly typed which means variable and parameters can be of any type. One of the
major downsides of this approach is in larger projects it can make code harder to follow and more bug prune. For
example, if you&rsquo;re expecting a variable to be an integer but turns out to be a string. Typescript makes bugs like
this much easier to catch because it is strongly typed and each variable and parameter is given a type.
Lets say you have the following function.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">add</span> <span class="o">=</span> <span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="nx">x</span> <span class="o">+</span> <span class="nx">y</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>Now we expect <code>x</code> and <code>y</code> to be integers here of course however we are not checking types so let&rsquo;s say we did the following.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">add</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">)</span> <span class="o">===</span> <span class="mi">5</span><span class="p">;</span> <span class="c1">// true
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">add</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="s2">&#34;3&#34;</span><span class="p">)</span> <span class="o">===</span> <span class="s2">&#34;23&#34;</span><span class="p">;</span> <span class="c1">// true
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">add</span><span class="p">(</span><span class="s2">&#34;2&#34;</span><span class="p">,</span> <span class="s2">&#34;3&#34;</span><span class="p">)</span> <span class="o">===</span> <span class="s2">&#34;23&#34;</span><span class="p">;</span> <span class="c1">// true
</span></span></span></code></pre></div><p>As you can see if you accidentally passed a string to <code>add</code> it returns a result we don&rsquo;t expect.
TS helps us catch theses types of errors. The following would be the equivalent functions
<code>add()</code> written in TS.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="nx">add</span> <span class="o">=</span> <span class="p">(</span><span class="nx">x</span>: <span class="kt">number</span><span class="p">,</span> <span class="nx">y</span>: <span class="kt">number</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="nx">x</span> <span class="o">+</span> <span class="nx">y</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><h2 id="definitely-typed">Definitely Typed</h2>
<p>When using JS libraries not written in TS we need a file which stores the type definitions of functions
and their parameters this is referred to as the global type definition file. Lots of popular libraries already
have this defined in a huge project on GitHub called <code>DefinitelyTyped</code>. You can actually add these to your
project by using <code>yarn add @types/&lt;package_name&gt;</code>.</p>
<p>This repo is huge and has over 5,000 libraries already defined
however for more obscure projects you may have to write you&rsquo;re own definitions. This then means we can take full
advantage of TS even with any external libraries we use. In this article, we will write definitions for
<code>react-native-canvas</code>.</p>
<ol>
<li>
<p>Fork the <code>DefinitelyTyped</code> project on GitHub,
<a href="https://help.github.com/en/articles/fork-a-repo">how to fork on GitHub</a>.</p>
</li>
<li>
<p>Git clone the project onto your computer, like so <code>git clone git@github.com:hmajid2301/DefinitelyTyped.git</code>.</p>
</li>
<li>
<p>Open the project in your favourite text editor and run the following commands in the root (project) directory.</p>
</li>
<li>
<p>Execute the following command using either <code>yarn</code> or <code>npm</code>, replace <code>react-native-canvas</code> with your package name.
Before you run the command you should make sure the package doesn&rsquo;t exist in which case all you likely need
to do is update its type definitions</p>
</li>
<li>
<p>You should see a new folder with the package name in the <code>types</code> folder.</p>
</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">yarn
</span></span><span class="line"><span class="cl">yarn npx dts-gen --dt --name react-native-canvas --template module
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># or</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">npm install
</span></span><span class="line"><span class="cl">npm npx dts-gen --dt --name react-native-canvas --template module
</span></span></code></pre></div><h2 id="tsconfigjson">tsconfig.json</h2>
<p>You should now have four auto-generated files, we can leave <code>tslint.json</code> as it is. Since this a
React Native library we will have to edit <code>tsconfig.json</code> with some new parameters. If you&rsquo;re confused
you can take a look at other type packages to see how they&rsquo;ve changed the <code>tsconfig</code> file. There are
plenty of React Native examples to take a look at. The <code>tsconfig</code> now looks like this</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;compilerOptions&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;module&#34;</span><span class="p">:</span> <span class="s2">&#34;commonjs&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;lib&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;es6&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;noImplicitAny&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;noImplicitThis&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;strictNullChecks&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;strictFunctionTypes&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;baseUrl&#34;</span><span class="p">:</span> <span class="s2">&#34;../&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;typeRoots&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;../&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;types&#34;</span><span class="p">:</span> <span class="p">[],</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;noEmit&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;forceConsistentCasingInFileNames&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;jsx&#34;</span><span class="p">:</span> <span class="s2">&#34;react-native&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;files&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;index.d.ts&#34;</span><span class="p">,</span> <span class="s2">&#34;react-native-canvas-tests.tsx&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h2 id="indexdts">index.d.ts</h2>
<p>Now onto the main file to edit index this contains the types for the library. So now we will have to look at the library
itself and take a look at the functions components etc. If the <code>index.d.ts</code> file has been created properly at the top
in comments you should see something like this.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="c1">// Type definitions for react-native-canvas 0.1
</span></span></span><span class="line"><span class="cl"><span class="c1">// Project: https://github.com/iddan/react-native-canvas#readme
</span></span></span><span class="line"><span class="cl"><span class="c1">// Definitions by: hmajid2301 &lt;https://github.com/hmajid2301&gt;
</span></span></span><span class="line"><span class="cl"><span class="c1">// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
</span></span></span><span class="line"><span class="cl"><span class="c1">// TypeScript Version: 3.1
</span></span></span></code></pre></div><p>The first two lines are auto-generated, the next line I added my name and the URL to my GitHub account. The following line
is also auto-generated and the final line is required because we are defining our types with <code>react-native-canvas</code>.</p>
<p>Now we actually need to look at the <code>react-native-canvas</code> library, so we know how to define our types correctly.
The source code is in the folder <code>src</code>, now the first class I use is <code>Canvas</code>. Here is a small snippet of the
source code.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="p">...</span>
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="kr">class</span> <span class="nx">Canvas</span> <span class="kr">extends</span> <span class="nx">Component</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">static</span> <span class="nx">propTypes</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">style</span><span class="o">:</span> <span class="nx">PropTypes</span><span class="p">.</span><span class="nx">shape</span><span class="p">(</span><span class="nx">ViewStylePropTypes</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nx">baseUrl</span><span class="o">:</span> <span class="nx">PropTypes</span><span class="p">.</span><span class="nx">string</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">originWhitelist</span><span class="o">:</span> <span class="nx">PropTypes</span><span class="p">.</span><span class="nx">arrayOf</span><span class="p">(</span><span class="nx">PropTypes</span><span class="p">.</span><span class="nx">string</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">  <span class="p">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The main thing I am interested in is the <code>props</code> we will need to define these in the <code>index.d.ts</code> file. So here we have a
React Native component class <code>export default class Canvas extends Component</code>, in the <code>index.d.ts</code> file this will become
<code>export default class Canvas extends React.Component&lt;CanvasProps&gt;</code> in this class, we don&rsquo;t have any state if we did
then it would look like <code>export default class Canvas extends React.Component&lt;CanvasProps, StateProps&gt;</code>.</p>
<p>Now we&rsquo;ve defined our class lets define our props we will define our props as an interface called <code>CanvasProps</code> which will
be defined like so.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">interface</span> <span class="nx">CanvasProps</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">style?</span>: <span class="kt">StyleProp</span><span class="p">&lt;</span><span class="nt">ViewStyle</span><span class="p">&gt;;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">baseUrl?</span>: <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">originWhitelist?</span>: <span class="kt">string</span><span class="p">[];</span>
</span></span><span class="line"><span class="cl">  <span class="nx">ref</span><span class="o">:</span> <span class="p">(</span><span class="nx">canvas</span>: <span class="kt">Canvas</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="kt">any</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The first objects are the same as the first three prop types in the original JS library. They are defined
almost exactly the same bar some syntax differences, in JS <code>style: PropTypes.shape(ViewStylePropTypes)</code> as a pose to
<code>style?: StyleProp&lt;ViewStyle&gt;</code> in TS. However in the original, the <code>ref</code> prop is not defined, so we define it ourselves
for completeness, <code>ref: (canvas: Canvas) =&gt; any</code>. In this case, the <code>ref</code> prop takes an input of type <code>Canvas</code> and can
return anything. Below is an example of <code>ref</code> being used (in JS).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kr">class</span> <span class="nx">App</span> <span class="kr">extends</span> <span class="nx">Component</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">handleCanvas</span> <span class="o">=</span> <span class="p">(</span><span class="nx">canvas</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">ctx</span> <span class="o">=</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">getContext</span><span class="p">(</span><span class="s2">&#34;2d&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">ctx</span><span class="p">.</span><span class="nx">fillStyle</span> <span class="o">=</span> <span class="s2">&#34;purple&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">ctx</span><span class="p">.</span><span class="nx">fillRect</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">100</span><span class="p">,</span> <span class="mi">100</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">render</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">&lt;</span><span class="nt">Canvas</span> <span class="na">ref</span><span class="o">=</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">handleCanvas</span><span class="p">}</span> <span class="p">/&gt;;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>In our <code>Canvas</code> class, we have to define our properties, according to the documentation we have the following
functions/attributes.</p>
<ul>
<li>Canvas#height</li>
<li>Canvas#width</li>
<li>Canvas#getContext()</li>
<li>Canvas#toDataURL()</li>
</ul>
<p>These get defined as follows;</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="nx">width</span>: <span class="kt">number</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="nx">height</span>: <span class="kt">number</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="nx">toDataURL</span><span class="o">:</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="kt">string</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="nx">getContext</span><span class="o">:</span> <span class="p">(</span><span class="nx">context</span>: <span class="kt">string</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">CanvasRenderingContext2D</span><span class="p">;</span>
</span></span></code></pre></div><p>This should all be pretty straight forward, the final property <code>getContext</code> returns <code>CanvasRenderingContext2D</code>.
This another interface we define using the <code>CanvasRenderingContext2D.js</code> class (separate file in <code>src</code> folder).
It&rsquo;s quite a long interface so if you want to see it
<a href="https://github.com/hmajid2301/DefinitelyTyped/blob/master/types/react-native-canvas/index.d.ts">here</a>.</p>
<p>We then repeat this process for the remaining classes, <code>Image</code>, <code>ImageData</code> which look like follows. In these classes,
we also define the constructor, which just contains the arguments and the type of object they expect. Note that these
classes aren&rsquo;t React Native components so we define them as normal classes. We also give them named exports i.e.
<code>export class Image</code> rather than <code>export default class Image</code>, this is because this is how they are defined in the
<code>react-native-canvas</code> library.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">class</span> <span class="nx">Image</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">constructor</span><span class="p">(</span><span class="nx">canvas</span>: <span class="kt">Canvas</span><span class="p">,</span> <span class="nx">height?</span>: <span class="kt">number</span><span class="p">,</span> <span class="nx">width?</span>: <span class="kt">number</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nx">crossOrigin</span>: <span class="kt">string</span> <span class="o">|</span> <span class="kc">undefined</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">height</span>: <span class="kt">number</span> <span class="o">|</span> <span class="kc">undefined</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">width</span>: <span class="kt">number</span> <span class="o">|</span> <span class="kc">undefined</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">src</span>: <span class="kt">string</span> <span class="o">|</span> <span class="kc">undefined</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">addEventListener</span><span class="o">:</span> <span class="p">(</span><span class="nx">event</span>: <span class="kt">string</span><span class="p">,</span> <span class="nx">func</span><span class="o">:</span> <span class="p">(...</span><span class="nx">args</span>: <span class="kt">any</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="kt">any</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="k">void</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">class</span> <span class="nx">ImageData</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">constructor</span><span class="p">(</span><span class="nx">canvas</span>: <span class="kt">Canvas</span><span class="p">,</span> <span class="nx">data</span>: <span class="kt">number</span><span class="p">[],</span> <span class="nx">height</span>: <span class="kt">number</span><span class="p">,</span> <span class="nx">width</span>: <span class="kt">number</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="kr">readonly</span> <span class="nx">data</span>: <span class="kt">number</span><span class="p">[];</span>
</span></span><span class="line"><span class="cl">  <span class="kr">readonly</span> <span class="nx">height</span>: <span class="kt">number</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kr">readonly</span> <span class="nx">width</span>: <span class="kt">number</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The final class to define is <code>Path2D</code>, which looks like</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">class</span> <span class="nx">Path2D</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">constructor</span><span class="p">(</span><span class="nx">canvas</span>: <span class="kt">Canvas</span><span class="p">,</span> <span class="p">...</span><span class="nx">args</span>: <span class="kt">any</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nx">addPath</span><span class="o">:</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nx">path</span>: <span class="kt">Path2D</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">transform</span><span class="o">?:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">a</span>: <span class="kt">number</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="nx">b</span>: <span class="kt">number</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="nx">c</span>: <span class="kt">number</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="nx">d</span>: <span class="kt">number</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="nx">e</span>: <span class="kt">number</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="nx">f</span>: <span class="kt">number</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span> <span class="o">=&gt;</span> <span class="k">void</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">closePath</span>: <span class="kt">CanvasRenderingContext2D</span><span class="p">[</span><span class="s2">&#34;closePath&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="nx">moveTo</span>: <span class="kt">CanvasRenderingContext2D</span><span class="p">[</span><span class="s2">&#34;moveTo&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="nx">lineTo</span>: <span class="kt">CanvasRenderingContext2D</span><span class="p">[</span><span class="s2">&#34;lineTo&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="nx">bezierCurveTo</span>: <span class="kt">CanvasRenderingContext2D</span><span class="p">[</span><span class="s2">&#34;bezierCurveTo&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="nx">quadraticCurveTo</span>: <span class="kt">CanvasRenderingContext2D</span><span class="p">[</span><span class="s2">&#34;quadraticCurveTo&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="nx">arc</span>: <span class="kt">CanvasRenderingContext2D</span><span class="p">[</span><span class="s2">&#34;arc&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="nx">arcTo</span>: <span class="kt">CanvasRenderingContext2D</span><span class="p">[</span><span class="s2">&#34;arcTo&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="nx">ellipse</span>: <span class="kt">CanvasRenderingContext2D</span><span class="p">[</span><span class="s2">&#34;ellipse&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="nx">rect</span>: <span class="kt">CanvasRenderingContext2D</span><span class="p">[</span><span class="s2">&#34;rect&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Again this class is very similar to the classes defined above except some of the properties look like
<code>closePath: CanvasRenderingContext2D[&quot;closePath&quot;]</code>. This is because <code>closePath</code> shares the same definition
as closePath in <code>CanvasRenderingContext2D</code>, which is defined as <code>closePath: () =&gt; void</code>. So rather than define
it twice we just copy the definition in <code>CanvasRenderingContext2D</code>.</p>
<h2 id="react-native-canvas-testsjsx">react-native-canvas-tests.jsx</h2>
<p>This is where we define some tests how the library should be used and their props types.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="o">*</span> <span class="kr">as</span> <span class="nx">React</span> <span class="kr">from</span> <span class="s2">&#34;react&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">View</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;react-native&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">Canvas</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">Image</span> <span class="kr">as</span> <span class="nx">CanvasImage</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">Path2D</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">ImageData</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;react-native-canvas&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">class</span> <span class="nx">CanvasTest</span> <span class="kr">extends</span> <span class="nx">React</span><span class="p">.</span><span class="nx">Component</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">render() {</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">View</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">                <span class="p">&lt;</span><span class="nt">Canvas</span> <span class="na">ref</span><span class="o">=</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">handleCanvas</span><span class="p">}</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;/</span><span class="nt">View</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">...</span>
</span></span></code></pre></div><p>So we import our library then we render our <code>Canvas</code> component.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="nx">handleCanvas</span> <span class="o">=</span> <span class="p">(</span><span class="nx">canvas</span>: <span class="kt">Canvas</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">canvas</span><span class="p">.</span><span class="nx">width</span> <span class="o">=</span> <span class="mi">100</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">canvas</span><span class="p">.</span><span class="nx">height</span> <span class="o">=</span> <span class="mi">100</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">context</span> <span class="o">=</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">getContext</span><span class="p">(</span><span class="s2">&#34;2d&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nx">context</span><span class="p">.</span><span class="nx">fillStyle</span> <span class="o">=</span> <span class="s2">&#34;purple&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">context</span><span class="p">.</span><span class="nx">fillRect</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">100</span><span class="p">,</span> <span class="mi">100</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">ellipse</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Path2D</span><span class="p">(</span><span class="nx">canvas</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nx">ellipse</span><span class="p">.</span><span class="nx">ellipse</span><span class="p">(</span><span class="mi">50</span><span class="p">,</span> <span class="mi">50</span><span class="p">,</span> <span class="mi">25</span><span class="p">,</span> <span class="mi">35</span><span class="p">,</span> <span class="p">(</span><span class="mi">45</span> <span class="o">*</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">PI</span><span class="p">)</span> <span class="o">/</span> <span class="mi">180</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">2</span> <span class="o">*</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">PI</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nx">context</span><span class="p">.</span><span class="nx">fillStyle</span> <span class="o">=</span> <span class="s2">&#34;purple&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">context</span><span class="p">.</span><span class="nx">fill</span><span class="p">(</span><span class="nx">ellipse</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">image</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">CanvasImage</span><span class="p">(</span><span class="nx">canvas</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nx">canvas</span><span class="p">.</span><span class="nx">width</span> <span class="o">=</span> <span class="mi">100</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">canvas</span><span class="p">.</span><span class="nx">height</span> <span class="o">=</span> <span class="mi">100</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">image</span><span class="p">.</span><span class="nx">src</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;https://upload.wikimedia.org/wikipedia/commons/6/63/Biho_Takashi._Bat_Before_the_Moon%2C_ca._1910.jpg&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">image</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s2">&#34;load&#34;</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">context</span><span class="p">.</span><span class="nx">drawImage</span><span class="p">(</span><span class="nx">image</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">100</span><span class="p">,</span> <span class="mi">100</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">imageData</span> <span class="o">=</span> <span class="nx">context</span><span class="p">.</span><span class="nx">getImageData</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">100</span><span class="p">,</span> <span class="mi">100</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">values</span><span class="p">(</span><span class="nx">imageData</span><span class="p">.</span><span class="nx">data</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">length</span> <span class="o">=</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">keys</span><span class="p">(</span><span class="nx">data</span><span class="p">).</span><span class="nx">length</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">length</span><span class="p">;</span> <span class="nx">i</span> <span class="o">+=</span> <span class="mi">4</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">data</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">data</span><span class="p">[</span><span class="nx">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">data</span><span class="p">[</span><span class="nx">i</span> <span class="o">+</span> <span class="mi">2</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">imgData</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ImageData</span><span class="p">(</span><span class="nx">canvas</span><span class="p">,</span> <span class="nx">data</span><span class="p">,</span> <span class="mi">100</span><span class="p">,</span> <span class="mi">100</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="nx">context</span><span class="p">.</span><span class="nx">putImageData</span><span class="p">(</span><span class="nx">imgData</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>Then in <code>handleCanvas</code>, we test out the different classes we defined, include <code>Canvas, ImageData, Image and Path2D</code> and that&rsquo;s it.
The above example is taken from a few examples in <code>example/App.js</code> within <code>react-native-canvas</code>. Ok now we&rsquo;ve defined our files
lets make sure the pull request (PR) will be accepted let&rsquo;s run <code>yarn run lint react-native-canvas</code>. If the linter doesn&rsquo;t complain then
we can commit and push our changes to our GitHub fork and
<a href="https://help.github.com/en/articles/creating-a-pull-request">make PR</a>.</p>
<hr>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://github.com">GitHub Account</a></li>
<li><a href="https://github.com/DefinitelyTyped/DefinitelyTyped">DefinitelyTyped</a></li>
<li><a href="https://github.com/hmajid2301/DefinitelyTyped/blob/master/types/react-native-canvas/index.d.ts">Source Code</a></li>
<li><a href="https://github.com/DefinitelyTyped/DefinitelyTyped/pull/33938">Example PR</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>DrawerNavigator, TabNavigator and StackNavigator with React Navigation</title>
      <link>https://haseebmajid.dev/posts/2019-03-04-drawernavigator-tabnavigator-and-stacknavigator-with-react-navigation/</link>
      <pubDate>Mon, 04 Mar 2019 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2019-03-04-drawernavigator-tabnavigator-and-stacknavigator-with-react-navigation/</guid>
      <description>&lt;p&gt;In this article, we will create a simple React Native (with &lt;a href=&#34;https://expo.io/&#34;&gt;Expo&lt;/a&gt;) application using React
Navigation, for moving between screens/pages within our app. We will create an app which had a Stack Navigator
nested in a Tab Navigator, nested within a Drawer Navigator.&lt;/p&gt;
&lt;p&gt;This might sound a bit complicated so let&amp;rsquo;s take a look gif below.&lt;/p&gt;
&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2019-03-04-drawernavigator-tabnavigator-and-stacknavigator-with-react-navigation/images/app.gif&#34;
        type=&#34;&#34;
        alt=&#34;GIF of App&#34;
        
      /&gt;&lt;/p&gt;
&lt;p&gt;We have three main pages in the Drawer Navigator Home (purple), Settings (red) and About (blue) pages. Inside
the Home page, we have a Tab Navigator which has two pages, PageA (purple) and PageB (green). Within PageA we have a
Stack Navigator with two pages Main (purple) and Secondary (yellow).&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this article, we will create a simple React Native (with <a href="https://expo.io/">Expo</a>) application using React
Navigation, for moving between screens/pages within our app. We will create an app which had a Stack Navigator
nested in a Tab Navigator, nested within a Drawer Navigator.</p>
<p>This might sound a bit complicated so let&rsquo;s take a look gif below.</p>
<p><img
        loading="lazy"
        src="/posts/2019-03-04-drawernavigator-tabnavigator-and-stacknavigator-with-react-navigation/images/app.gif"
        type=""
        alt="GIF of App"
        
      /></p>
<p>We have three main pages in the Drawer Navigator Home (purple), Settings (red) and About (blue) pages. Inside
the Home page, we have a Tab Navigator which has two pages, PageA (purple) and PageB (green). Within PageA we have a
Stack Navigator with two pages Main (purple) and Secondary (yellow).</p>
<h2 id="react-navigation">React Navigation</h2>
<p>Is a library that helps you simplify app navigation.
The main reason for using this library is because it&rsquo;s written purely in JavaScript so no native code (Swift/Android)
is required to make it work. Also, it&rsquo;s the
<a href="https://docs.expo.io/versions/latest/guides/routing-and-navigation">recommended navigation library</a> for Expo.</p>
<h2 id="structure">Structure</h2>
<p>The project structure will look like this</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">├── src
</span></span><span class="line"><span class="cl">│   ├── components
</span></span><span class="line"><span class="cl">│   ├── views
</span></span><span class="line"><span class="cl">|   └── MainApp.js
</span></span><span class="line"><span class="cl">├── package.json
</span></span><span class="line"><span class="cl">├── App.js
</span></span></code></pre></div><h2 id="drawer-navigator">Drawer Navigator</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">Ionicons</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;@expo/vector-icons&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span> <span class="nx">from</span> <span class="s2">&#34;react&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">createAppContainer</span><span class="p">,</span> <span class="nx">createDrawerNavigator</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;react-navigation&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">CustomDrawerNavigator</span> <span class="nx">from</span> <span class="s2">&#34;./components/CustomDrawerNavigator&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">Home</span> <span class="nx">from</span> <span class="s2">&#34;./views/Home&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">Settings</span> <span class="nx">from</span> <span class="s2">&#34;./views/Settings&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">About</span> <span class="nx">from</span> <span class="s2">&#34;./views/About&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">MainNavigator</span> <span class="o">=</span> <span class="nx">createDrawerNavigator</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">Home</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">navigationOptions</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">drawerIcon</span><span class="o">:</span> <span class="p">({</span> <span class="nx">tintColor</span> <span class="p">})</span> <span class="p">=&gt;</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="o">&lt;</span><span class="nx">Ionicons</span> <span class="nx">name</span><span class="o">=</span><span class="s2">&#34;md-home&#34;</span> <span class="nx">style</span><span class="o">=</span><span class="p">{{</span> <span class="nx">color</span><span class="o">:</span> <span class="nx">tintColor</span> <span class="p">}}</span> <span class="o">/&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="nx">drawerLabel</span><span class="o">:</span> <span class="s2">&#34;Home&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="nx">screen</span><span class="o">:</span> <span class="nx">Home</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nx">Settings</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">navigationOptions</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">drawerIcon</span><span class="o">:</span> <span class="p">({</span> <span class="nx">tintColor</span> <span class="p">})</span> <span class="p">=&gt;</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="o">&lt;</span><span class="nx">Ionicons</span> <span class="nx">name</span><span class="o">=</span><span class="s2">&#34;md-settings&#34;</span> <span class="nx">style</span><span class="o">=</span><span class="p">{{</span> <span class="nx">color</span><span class="o">:</span> <span class="nx">tintColor</span> <span class="p">}}</span> <span class="o">/&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="nx">drawerLabel</span><span class="o">:</span> <span class="s2">&#34;Settings&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="nx">screen</span><span class="o">:</span> <span class="nx">Settings</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nx">About</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">navigationOptions</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">drawerIcon</span><span class="o">:</span> <span class="p">({</span> <span class="nx">tintColor</span> <span class="p">})</span> <span class="p">=&gt;</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="o">&lt;</span><span class="nx">Ionicons</span> <span class="nx">name</span><span class="o">=</span><span class="s2">&#34;ios-person&#34;</span> <span class="nx">style</span><span class="o">=</span><span class="p">{{</span> <span class="nx">color</span><span class="o">:</span> <span class="nx">tintColor</span> <span class="p">}}</span> <span class="o">/&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="nx">drawerLabel</span><span class="o">:</span> <span class="s2">&#34;About&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="nx">screen</span><span class="o">:</span> <span class="nx">About</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">contentComponent</span><span class="o">:</span> <span class="nx">CustomDrawerNavigator</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">MainApp</span> <span class="o">=</span> <span class="nx">createAppContainer</span><span class="p">(</span><span class="nx">MainNavigator</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">MainApp</span><span class="p">;</span>
</span></span></code></pre></div><p>This is where we create our DrawerNavigator with our three main pages, let&rsquo;s take a look at a particular page. Here we
give it the key name <code>Home</code>, the <code>drawerIcon</code> relates to what Icon will be shown in the Drawer Navigator, the <code>drawerLabel</code>
is the name of the page, as shown in the image below. Finally, the <code>screen</code> is the actual &ldquo;jsx&rdquo; the user sees when they
click on the page. By default, the <code>Home</code> page will be the first page the user sees because it is the first page
defined in the navigator.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="nx">Home</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">navigationOptions</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">drawerIcon</span><span class="o">:</span> <span class="p">({</span> <span class="nx">tintColor</span> <span class="p">})</span> <span class="p">=&gt;</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">Ionicons</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;md-home&#34;</span> <span class="na">style</span><span class="o">=</span><span class="p">{{</span> <span class="nx">color</span><span class="o">:</span> <span class="nx">tintColor</span> <span class="p">}}</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="nx">drawerLabel</span><span class="o">:</span> <span class="s2">&#34;Home&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nx">screen</span><span class="o">:</span> <span class="nx">Home</span>
</span></span><span class="line"><span class="cl"><span class="p">},</span>
</span></span></code></pre></div><p><img
        loading="lazy"
        src="/posts/2019-03-04-drawernavigator-tabnavigator-and-stacknavigator-with-react-navigation/images/drawernavigator.png"
        type=""
        alt="The Drawer Navigator"
        
      /></p>
<h3 id="custom-drawer-navigator">Custom Drawer Navigator</h3>
<p>After we&rsquo;ve defined our three pages we then define a custom Drawer Navigator <code>contentComponent: CustomDrawerNavigator</code>.
This is a component we make ourselves and can style it however we wish.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span> <span class="nx">from</span> <span class="s2">&#34;react&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">View</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;react-native&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">DrawerItems</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;react-navigation&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">styles</span> <span class="nx">from</span> <span class="s2">&#34;./styles&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">CustomDrawerNavigator</span> <span class="o">=</span> <span class="nx">props</span> <span class="p">=&gt;</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">View</span> <span class="na">style</span><span class="o">=</span><span class="p">{[</span><span class="nx">styles</span><span class="p">.</span><span class="nx">container</span><span class="p">]}&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">DrawerItems</span>
</span></span><span class="line"><span class="cl">      <span class="na">activeBackgroundColor</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;black&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="na">activeTintColor</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;white&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="na">iconContainerStyle</span><span class="o">=</span><span class="p">{</span><span class="nx">styles</span><span class="p">.</span><span class="nx">icons</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span><span class="na">...props</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">View</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">CustomDrawerNavigator</span><span class="p">;</span>
</span></span></code></pre></div><p>Here the <code>DrawerItems</code> are the three pages we&rsquo;ve defined above. We give the active page, the page the user is currently
on, a background of black and a colour of white (the text). The line <code>{...props}</code> is using the
<a href="https://stackoverflow.com/questions/31048953/what-do-these-three-dots-in-react-do">spread operator (&hellip;)</a>,
so we don&rsquo;t have to rewrite all the props passed by React Navigation. For example, we have props like <code>onItemPress</code>
or <code>navigation</code>. This is the same Drawer Navigator shown in the image above.</p>
<h2 id="tab-navigator">Tab Navigator</h2>
<p>So our Tab Navigator allow us to swap pages by changing tabs. The Tab Navigator will be on the home page, so
<code>Home.js</code> has the following code.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span><span class="p">,</span> <span class="p">{</span> <span class="nx">Component</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;react&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">View</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;react-native&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">CustomTabNavigator</span> <span class="nx">from</span> <span class="s2">&#34;../components/CustomTabNavigator&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">CustomHeader</span> <span class="nx">from</span> <span class="s2">&#34;../components/CustomHeader&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="kr">class</span> <span class="nx">Home</span> <span class="kr">extends</span> <span class="nx">Component</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">static</span> <span class="nx">router</span> <span class="o">=</span> <span class="nx">CustomTabNavigator</span><span class="p">.</span><span class="nx">router</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">render</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="o">&lt;</span><span class="nx">View</span> <span class="nx">style</span><span class="o">=</span><span class="p">{{</span> <span class="nx">flex</span><span class="o">:</span> <span class="mi">1</span> <span class="p">}}</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="o">&lt;</span><span class="nx">CustomHeader</span> <span class="nx">navigation</span><span class="o">=</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">props</span><span class="p">.</span><span class="nx">navigation</span><span class="p">}</span> <span class="o">/&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="o">&lt;</span><span class="nx">CustomTabNavigator</span> <span class="nx">navigation</span><span class="o">=</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">props</span><span class="p">.</span><span class="nx">navigation</span><span class="p">}</span> <span class="o">/&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="o">&lt;</span><span class="err">/View&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="custom-header">Custom Header</h3>
<p>Where the custom header looks like so.</p>
<p><img
        loading="lazy"
        src="/posts/2019-03-04-drawernavigator-tabnavigator-and-stacknavigator-with-react-navigation/images/WhiteHeader.png"
        type=""
        alt="CustomHeader"
        
      /></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">Ionicons</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;@expo/vector-icons&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span> <span class="nx">from</span> <span class="s2">&#34;react&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">View</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;react-native&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">styles</span> <span class="nx">from</span> <span class="s2">&#34;./styles&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">CustomHeader</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">navigation</span> <span class="p">})</span> <span class="p">=&gt;</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">View</span> <span class="na">style</span><span class="o">=</span><span class="p">{[</span><span class="nx">styles</span><span class="p">.</span><span class="nx">container</span><span class="p">]}&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">Ionicons</span>
</span></span><span class="line"><span class="cl">      <span class="na">name</span><span class="o">=</span><span class="s">&#34;md-menu&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="na">size</span><span class="o">=</span><span class="p">{</span><span class="mi">32</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="na">color</span><span class="o">=</span><span class="s">&#34;black&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="na">onPress</span><span class="o">=</span><span class="p">{()</span> <span class="p">=&gt;</span> <span class="nx">navigation</span><span class="p">.</span><span class="nx">openDrawer</span><span class="p">()}</span>
</span></span><span class="line"><span class="cl">    <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;/</span><span class="nt">View</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">CustomHeader</span><span class="p">;</span>
</span></span></code></pre></div><p>This component is just a simple icon, which when pressed, opens the Drawer Navigator using the <code>navigation</code> props we
passed to the component <code>onPress={() =&gt; navigation.openDrawer()}</code>.</p>
<p><strong>Note:</strong> We could also swipe right to open the Drawer Navigator (from the left hand side of the screen).</p>
<p>In the parameters we use the following syntax <code>({ navigation })</code>, this is called
<a href="https://hacks.mozilla.org/2015/05/es6-in-depth-destructuring/">destructuring</a>. We can use this with arrays or
objects for example;</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">temp</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">a</span><span class="o">:</span> <span class="mi">10</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">b</span><span class="o">:</span> <span class="mi">20</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// We could do
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="nx">a</span> <span class="o">=</span> <span class="nx">temp</span><span class="p">.</span><span class="nx">a</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">b</span> <span class="o">=</span> <span class="nx">temp</span><span class="p">.</span><span class="nx">b</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Or
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kr">const</span> <span class="p">{</span> <span class="nx">a</span><span class="p">,</span> <span class="nx">b</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">temp</span><span class="p">;</span>
</span></span></code></pre></div><p>We do as similar thing with the props, rather then referring to <code>navigation</code> as <code>props.navigation</code> and passing a
parameter <code>props</code> instead of <code>({ navigation })</code>, this simplifies our code a little bit and makes it easier to follow.</p>
<h3 id="custom-tab-navigator">Custom Tab Navigator</h3>
<p>The custom Tab Navigator is similar to the <code>MainApp.js</code>&rsquo;s Drawer Navigator.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">createMaterialTopTabNavigator</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;react-navigation&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">PageA</span> <span class="nx">from</span> <span class="s2">&#34;../../views/Home/PageA&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">PageB</span> <span class="nx">from</span> <span class="s2">&#34;../../views/Home/PageB&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">CustomTabNavigator</span> <span class="o">=</span> <span class="nx">createMaterialTopTabNavigator</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">PageA</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">navigationOptions</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">tabBarLabel</span><span class="o">:</span> <span class="s2">&#34;PageA&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="nx">screen</span><span class="o">:</span> <span class="nx">PageA</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nx">PageB</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">navigationOptions</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">tabBarLabel</span><span class="o">:</span> <span class="s2">&#34;PageB&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="nx">screen</span><span class="o">:</span> <span class="nx">PageB</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">tabBarPosition</span><span class="o">:</span> <span class="s2">&#34;bottom&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">CustomTabNavigator</span><span class="p">;</span>
</span></span></code></pre></div><p>We give it the two pages we want to be &ldquo;navigatable&rdquo; via tabs in this case <code>PageA</code> and <code>PageB</code>, we then define
the tab to be at the bottom (rather than the top) of the screen. By default <code>PageA</code> will be the page we
visit when we go <code>Home</code> page because it&rsquo;s the first page defined in the Tab Navigator.</p>
<hr>
<h2 id="stack-navigator">Stack Navigator</h2>
<p>In this example, PageB (second page on Tab Navigator) is just a simple page. The stack navigator exists on
<code>PageA</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">createStackNavigator</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;react-navigation&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">Main</span> <span class="nx">from</span> <span class="s2">&#34;./PageA/Main&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">Secondary</span> <span class="nx">from</span> <span class="s2">&#34;./PageA/Secondary&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">PageANavigator</span> <span class="o">=</span> <span class="nx">createStackNavigator</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nx">Main</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">navigationOptions</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">header</span><span class="o">:</span> <span class="kc">null</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nx">screen</span><span class="o">:</span> <span class="nx">Main</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">Secondary</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">navigationOptions</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">header</span><span class="o">:</span> <span class="kc">null</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nx">screen</span><span class="o">:</span> <span class="nx">Secondary</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">PageANavigator</span><span class="p">.</span><span class="nx">navigationOptions</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">navigation</span> <span class="p">})</span> <span class="p">=&gt;</span> <span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nx">tabBarVisible</span><span class="o">:</span> <span class="nx">navigation</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">index</span> <span class="o">===</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">swipeEnabled</span><span class="o">:</span> <span class="nx">navigation</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">index</span> <span class="o">===</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">PageANavigator</span><span class="p">;</span>
</span></span></code></pre></div><p>Again this is very similar to our other navigators we&rsquo;ve defined. Here we define two pages that we can navigate between,
like before the <code>Main</code> page is the page we visit by default. So the first page the user will see when you open the app
is the <code>Main</code> page. This is because when the app is first opened we visit the <code>Home</code> page (first in the Drawer Navigator),
which contains <code>PageA</code> (first in Tab Navigator), which contains <code>Main</code> (first in Stack Navigator).</p>
<p>In the code, we set <code>header: null</code> else part of the screen will be taken up by a navigation header, as shown in the
image below.</p>
<p><img
        loading="lazy"
        src="/posts/2019-03-04-drawernavigator-tabnavigator-and-stacknavigator-with-react-navigation/images/WhiteHeader.png"
        type=""
        alt="The White Header"
        
      /></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="nx">PageANavigator</span><span class="p">.</span><span class="nx">navigationOptions</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">navigation</span> <span class="p">})</span> <span class="p">=&gt;</span> <span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nx">tabBarVisible</span><span class="o">:</span> <span class="nx">navigation</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">index</span> <span class="o">===</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">swipeEnabled</span><span class="o">:</span> <span class="nx">navigation</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">index</span> <span class="o">===</span> <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><p>The first page in the Stack Navigator (<code>Main</code>) has an index of 0, so <code>navigation.state.index === 0</code> evaluates as true.
So on the <code>Main</code> page we will see the tab bar and can swipe to change tabs but we cannot do this on the second page
<code>Secondary</code>.</p>
<h3 id="main">Main</h3>
<p>So now we have a Stack Navigator but we have no obvious way to get from <code>Main</code> to <code>Secondary</code>. With the
Drawer Navigator and Tab Navigator, we can press buttons to change.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span><span class="p">,</span> <span class="p">{</span> <span class="nx">Component</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;react&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">Button</span><span class="p">,</span> <span class="nx">View</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;react-native&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="kr">class</span> <span class="nx">Main</span> <span class="kr">extends</span> <span class="nx">Component</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">render</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="o">&lt;</span><span class="nx">View</span> <span class="nx">style</span><span class="o">=</span><span class="p">{{</span> <span class="nx">flex</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nx">backgroundColor</span><span class="o">:</span> <span class="s2">&#34;purple&#34;</span> <span class="p">}}</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="o">&lt;</span><span class="nx">Button</span>
</span></span><span class="line"><span class="cl">          <span class="nx">title</span><span class="o">=</span><span class="s2">&#34;To Secondary&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="nx">onPress</span><span class="o">=</span><span class="p">{()</span> <span class="p">=&gt;</span> <span class="k">this</span><span class="p">.</span><span class="nx">props</span><span class="p">.</span><span class="nx">navigation</span><span class="p">.</span><span class="nx">navigate</span><span class="p">(</span><span class="s2">&#34;Secondary&#34;</span><span class="p">)}</span>
</span></span><span class="line"><span class="cl">        <span class="o">/&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="o">&lt;</span><span class="err">/View&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>We can do this by creating our button in <code>Main</code> which when pressed navigates us to <code>Secondary</code> page,
<code>onPress={() =&gt; this.props.navigation.navigate(&quot;Secondary&quot;)}</code>. Where &ldquo;Secondary&rdquo; is the page name to
change to.</p>
<h2 id="other-pages">Other Pages</h2>
<p>All the other pages are very basic and look something like this, simple background and not much else.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span><span class="p">,</span> <span class="p">{</span> <span class="nx">Component</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;react&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">View</span> <span class="p">}</span> <span class="nx">from</span> <span class="s2">&#34;react-native&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">CustomHeader</span> <span class="nx">from</span> <span class="s2">&#34;../components/CustomHeader&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="kr">class</span> <span class="nx">Settings</span> <span class="kr">extends</span> <span class="nx">Component</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">render</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="o">&lt;</span><span class="nx">View</span> <span class="nx">style</span><span class="o">=</span><span class="p">{{</span> <span class="nx">flex</span><span class="o">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nx">backgroundColor</span><span class="o">:</span> <span class="s2">&#34;red&#34;</span> <span class="p">}}</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="o">&lt;</span><span class="nx">CustomHeader</span> <span class="nx">navigation</span><span class="o">=</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">props</span><span class="p">.</span><span class="nx">navigation</span><span class="p">}</span> <span class="o">/&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="o">&lt;</span><span class="err">/View&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2019-03-04-drawernavigator-tabnavigator-and-stacknavigator-with-react-navigation/source_code">Example source code</a></li>
<li><a href="https://reactnavigation.org/">React Navigation</a></li>
<li><a href="https://www.genymotion.com/">Genymotion emulator</a></li>
<li>GIFs created with <a href="https://www.screentogif.com/">screentogif</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Theme your Expo/React Native App with Redux and React Navigation</title>
      <link>https://haseebmajid.dev/posts/2018-12-23-theme-your-expo-react-native-app-with-redux-and-react-navigation/</link>
      <pubDate>Sun, 23 Dec 2018 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2018-12-23-theme-your-expo-react-native-app-with-redux-and-react-navigation/</guid>
      <description>&lt;p&gt;Recently whilst developing a React Native app (with Expo),
I built a simple tab navigator using React Navigation library.
I wanted to theme the app so that the header would change colour depending on which page you&amp;rsquo;re on.
For example on the primary page it would be red and on the secondary page, when you change tabs, the
header would become blue.&lt;/p&gt;
&lt;p&gt;I ended up being able to do it using Redux. In this article, I will show you how you can theme your Expo
app using Redux with React Navigation.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Recently whilst developing a React Native app (with Expo),
I built a simple tab navigator using React Navigation library.
I wanted to theme the app so that the header would change colour depending on which page you&rsquo;re on.
For example on the primary page it would be red and on the secondary page, when you change tabs, the
header would become blue.</p>
<p>I ended up being able to do it using Redux. In this article, I will show you how you can theme your Expo
app using Redux with React Navigation.</p>
<p>This article is written in British English however, the code is written using American English to make it more consistent (colour vs color).</p>
<h2 id="react-navigation">React Navigation</h2>
<p>Is a library that helps you simplify app navigation.
The main reason for using this library is because it&rsquo;s written purely in JavaScript so no native code (Swift/Android) is required to make it work. Also, it&rsquo;s the <a href="https://docs.expo.io/versions/latest/guides/routing-and-navigation">recommended navigation library</a> for Expo.</p>
<h2 id="redux">Redux</h2>
<p><a href="https://redux.js.org/">Redux</a> is used to manage global state across a React app, it can actually be used with both React and React Native. Of course, in this example, we will be using Redux with a React Native app.</p>
<p>
  <img
    loading="lazy"
    src="https://css-tricks.com/wp-content/uploads/2016/03/redux-article-3-01.svg"
    alt="Redux Store, from https://css-tricks.com/learning-react-redux/"
    
  /></p>
<p>The diagram above explains pretty well why you may need to use Redux. Passing state can be tricky
in more complicated React Native apps, with lots of components and sub-components. Using redux
we can change the state in one component and Redux will update the state in all the other components.</p>
<p>There are many great tutorials about Redux, I particularly liked this <a href="https://www.youtube.com/watch?v=KcC8KZ_Ga2M">one</a>.
Here is brief summary how Redux will work with this app.</p>
<ul>
<li>Redux sets the initial (state) colour to red</li>
<li>Change tabs from main to the secondary page</li>
<li>This dispatches an action to the Redux store (action is called <code>toggleTheme</code>)</li>
<li>The store then calls a reducer</li>
<li>The reducer (also called <code>toggleTheme</code>) updates the old state to a new state, it changes the colour from red to blue</li>
<li>Redux updates the state in all the components</li>
</ul>
<p>Here is a GIF that might help clear up how it work.</p>
<p>
  <img
    loading="lazy"
    src="https://camo.githubusercontent.com/5aba89b6daab934631adffc1f301d17bb273268b/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6d656469612d702e736c69642e65732f75706c6f6164732f3336343831322f696d616765732f323438343535322f415243482d5265647578322d7265616c2e676966"
    alt="Redux Flow, from http://slides.com/jenyaterpil/redux-from-twitter-hype-to-production#"
    
  /></p>
<h2 id="prerequisite">Prerequisite</h2>
<ul>
<li>An Expo/React Native app</li>
<li>An Android emulator/device to test on</li>
<li>Install the following dependencies</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">&#34;react-navigation&#34;: &#34;^2.18.0&#34;,
</span></span><span class="line"><span class="cl">&#34;react-redux&#34;: &#34;^5.1.1&#34;,
</span></span><span class="line"><span class="cl">&#34;redux&#34;: &#34;^4.0.1&#34;,
</span></span></code></pre></div><h2 id="solution">Solution</h2>
<p>Now to the interesting part of this article let&rsquo;s take a look at the code.</p>
<h3 id="structure">Structure</h3>
<p>The project structure will look like this</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">├── src
</span></span><span class="line"><span class="cl">│   ├── actions
</span></span><span class="line"><span class="cl">│   ├── components
</span></span><span class="line"><span class="cl">│   ├── containers
</span></span><span class="line"><span class="cl">│   ├── reducers
</span></span><span class="line"><span class="cl">│   ├── screens
</span></span><span class="line"><span class="cl">│   ├── store
</span></span><span class="line"><span class="cl">|   └── themes
</span></span><span class="line"><span class="cl">├── package.json
</span></span><span class="line"><span class="cl">├── App.js
</span></span></code></pre></div><h3 id="themes">themes</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="nx">COLORS</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">red</span><span class="o">:</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">&#34;red&#34;</span><span class="p">,</span> <span class="nx">hexCode</span><span class="o">:</span> <span class="s2">&#34;#DE5448&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nx">blue</span><span class="o">:</span> <span class="p">{</span> <span class="nx">name</span><span class="o">:</span> <span class="s2">&#34;blue&#34;</span><span class="p">,</span> <span class="nx">hexCode</span><span class="o">:</span> <span class="s2">&#34;#498AF4&#34;</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>Here we define our two colours that will be used as themes, red and blue. I have given each colour a name because it makes the toggle logic easier to follow.</p>
<h3 id="reducers">reducers</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">COLORS</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;../themes&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">initialState</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">colorData</span><span class="o">:</span> <span class="nx">COLORS</span><span class="p">.</span><span class="nx">red</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">Theme</span> <span class="o">=</span> <span class="p">(</span><span class="nx">state</span> <span class="o">=</span> <span class="nx">initialState</span><span class="p">,</span> <span class="nx">action</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">switch</span> <span class="p">(</span><span class="nx">action</span><span class="p">.</span><span class="nx">type</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="s1">&#39;TOGGLE_THEME&#39;</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">      <span class="k">switch</span><span class="p">(</span><span class="nx">action</span><span class="p">.</span><span class="nx">payload</span><span class="p">.</span><span class="nx">name</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">case</span> <span class="s1">&#39;red&#39;</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">          <span class="k">return</span> <span class="p">{</span> <span class="nx">colorData</span><span class="o">:</span> <span class="nx">COLORS</span><span class="p">.</span><span class="nx">blue</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl">        <span class="k">case</span> <span class="s1">&#39;blue&#39;</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">          <span class="k">return</span> <span class="p">{</span> <span class="nx">colorData</span><span class="o">:</span> <span class="nx">COLORS</span><span class="p">.</span><span class="nx">red</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">default</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">      <span class="k">return</span> <span class="nx">state</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">Theme</span><span class="p">;</span>
</span></span></code></pre></div><p>A reducer is a pure function which takes some state and returns a new state. In this example
it gets passed the current theme colour and swaps it to the new theme colour.</p>
<p>In this example if the action is <code>TOGGLE_THEME</code>, we get the colour name from the payload and using a switch
statement we swap the colours over. So if the current colour is red we update the state (<code>colorData</code>) to be
blue.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">combineReducers</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;redux&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">Theme</span> <span class="nx">from</span> <span class="s1">&#39;./Theme&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">combineReducers</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nx">Theme</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><p>Here we combine all of our reducers, in this example, we are only using the one reducer but if we had multiple reducers, The <code>combineReducers</code> function to would be necessary to combine them together. In this
example we can simply add new reducers to the function as and when we need them.</p>
<h3 id="actions">actions</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="nx">TOGGLE_THEME</span> <span class="o">=</span> <span class="s2">&#34;TOGGLE_THEME&#34;</span><span class="p">;</span>
</span></span></code></pre></div><p>This file defines all the actions we can dispatch to our store. In this example, we only need one action to
toggle our theme.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">TOGGLE_THEME</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;./actionTypes&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="nx">toggleTheme</span> <span class="o">=</span> <span class="nx">theme</span> <span class="p">=&gt;</span> <span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nx">type</span><span class="o">:</span> <span class="nx">TOGGLE_THEME</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">payload</span><span class="o">:</span> <span class="nx">theme</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><p>In this file, we define our actions, so here we have a single action <code>toggleTheme</code>, which takes a
theme as input and passes it as our payload, hence to access the name of the colour we use <code>action.payload.name</code> in our reducer.</p>
<h3 id="store">store</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">createStore</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;redux&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">rootReducer</span> <span class="nx">from</span> <span class="s1">&#39;../reducers&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">store</span> <span class="o">=</span> <span class="nx">createStore</span><span class="p">(</span><span class="nx">rootReducer</span><span class="p">);</span>
</span></span></code></pre></div><p>The Redux store is used to store the current state for our app, we have to link our store with our reducers we can do this using the <code>createStore</code> function and import the reducers from <code>reducers/index.js</code>.</p>
<h3 id="appjs">App.js</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span><span class="p">,</span> <span class="p">{</span> <span class="nx">Component</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;react&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">Provider</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;react-redux&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">store</span> <span class="nx">from</span> <span class="s1">&#39;./src/store&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">CustomTabNavigator</span> <span class="nx">from</span> <span class="s1">&#39;./src/components/CustomTabNavigator&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="kr">class</span> <span class="nx">App</span> <span class="kr">extends</span> <span class="nx">Component</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">render</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="o">&lt;</span><span class="nx">Provider</span> <span class="nx">store</span><span class="o">=</span><span class="p">{</span><span class="nx">store</span><span class="p">}</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="o">&lt;</span><span class="nx">CustomTabNavigator</span> <span class="o">/&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="o">&lt;</span><span class="err">/Provider&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>This acts as the main file for our app, to use Redux with our app we must wrap around <code>Provider</code> tags and
set the store props to our <code>store/index.js</code> file. The <code>&lt;CustomTabNavigator&gt;</code> contains the logic for our two screens and the tab navigator.</p>
<h3 id="components">components</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">store</span> <span class="nx">from</span> <span class="s1">&#39;../store&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">createMaterialTopTabNavigator</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;react-navigation&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">PageA</span> <span class="nx">from</span> <span class="s1">&#39;../screens/PageA&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">PageB</span> <span class="nx">from</span> <span class="s1">&#39;../screens/PageB&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">COLORS</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;../themes&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">toggleTheme</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;../actions&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">commonTabOptions</span> <span class="o">=</span> <span class="nx">color</span> <span class="p">=&gt;</span> <span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nx">activeTintColor</span><span class="o">:</span> <span class="s1">&#39;white&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">pressColor</span><span class="o">:</span> <span class="s1">&#39;#fff&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">inactiveTintColor</span><span class="o">:</span> <span class="s1">&#39;#ddd&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">style</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">backgroundColor</span><span class="o">:</span> <span class="nx">color</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">CustomerTabNavigator</span> <span class="o">=</span> <span class="nx">createMaterialTopTabNavigator</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nx">Encoding</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">screen</span><span class="o">:</span> <span class="nx">PageA</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">navigationOptions</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">tabBarLabel</span><span class="o">:</span> <span class="s1">&#39;A&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">tabBarOptions</span><span class="o">:</span> <span class="nx">commonTabOptions</span><span class="p">(</span><span class="nx">COLORS</span><span class="p">.</span><span class="nx">red</span><span class="p">.</span><span class="nx">hexCode</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="nx">tabBarOnPress</span><span class="o">:</span> <span class="p">({</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">defaultHandler</span> <span class="p">})</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">store</span><span class="p">.</span><span class="nx">dispatch</span><span class="p">(</span><span class="nx">toggleTheme</span><span class="p">(</span><span class="nx">COLORS</span><span class="p">.</span><span class="nx">blue</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">        <span class="nx">defaultHandler</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nx">Decoding</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">screen</span><span class="o">:</span> <span class="nx">PageB</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">navigationOptions</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">tabBarLabel</span><span class="o">:</span> <span class="s1">&#39;B&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">tabBarOptions</span><span class="o">:</span> <span class="nx">commonTabOptions</span><span class="p">(</span><span class="nx">COLORS</span><span class="p">.</span><span class="nx">blue</span><span class="p">.</span><span class="nx">hexCode</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">      <span class="nx">tabBarOnPress</span><span class="o">:</span> <span class="p">({</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">defaultHandler</span> <span class="p">})</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">store</span><span class="p">.</span><span class="nx">dispatch</span><span class="p">(</span><span class="nx">toggleTheme</span><span class="p">(</span><span class="nx">COLORS</span><span class="p">.</span><span class="nx">red</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">        <span class="nx">defaultHandler</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">},</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">tabBarPosition</span><span class="o">:</span> <span class="s1">&#39;bottom&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">CustomerTabNavigator</span><span class="p">;</span>
</span></span></code></pre></div><p>Here is where we use react navigation to create our tab navigator. We define two screens called A and B,
each screen has a different tab colour, red for A and blue for B. The main part of this file is the following</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="nx">tabBarOnPress</span><span class="o">:</span> <span class="p">({</span> <span class="nx">defaultHandler</span> <span class="p">})</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">store</span><span class="p">.</span><span class="nx">dispatch</span><span class="p">(</span><span class="nx">toggleTheme</span><span class="p">(</span><span class="nx">COLORS</span><span class="p">.</span><span class="nx">red</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="nx">defaultHandler</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">},</span>
</span></span></code></pre></div><p>On tab change (from A -&gt; B), we detect the tab press and dispatch the <code>toggleTheme</code> action to the Redux
store. So when we change tabs from A -&gt; B the colour in the store will change from Red -&gt; Blue and
vice versa for B -&gt; A.</p>
<p>One other thing to note is the tab colour is set using the following function. The colour is passed by the <code>tabBarOptions</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">commonTabOptions</span> <span class="o">=</span> <span class="p">(</span><span class="nx">color</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nx">activeTintColor</span><span class="o">:</span> <span class="s2">&#34;white&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">pressColor</span><span class="o">:</span> <span class="s2">&#34;#fff&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">inactiveTintColor</span><span class="o">:</span> <span class="s2">&#34;#ddd&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">style</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">backgroundColor</span><span class="o">:</span> <span class="nx">color</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="nx">tabBarOptions</span><span class="o">:</span> <span class="nx">commonTabOptions</span><span class="p">(</span><span class="nx">COLORS</span><span class="p">.</span><span class="nx">red</span><span class="p">.</span><span class="nx">hexCode</span><span class="p">);</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span><span class="p">,</span> <span class="p">{</span> <span class="nx">Component</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;react&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">View</span><span class="p">,</span> <span class="nx">Button</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;react-native&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">connect</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;react-redux&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">toggleTheme</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;../actions&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">class</span> <span class="nx">ToggleTheme</span> <span class="kr">extends</span> <span class="nx">Component</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">render</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="o">&lt;</span><span class="nx">View</span> <span class="nx">style</span><span class="o">=</span><span class="p">{{</span><span class="nx">marginTop</span><span class="o">:</span> <span class="mi">25</span><span class="p">}}</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="o">&lt;</span><span class="nx">Button</span>
</span></span><span class="line"><span class="cl">          <span class="nx">title</span><span class="o">=</span><span class="s2">&#34;Toggle Color&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="nx">color</span><span class="o">=</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">props</span><span class="p">.</span><span class="nx">color</span><span class="p">.</span><span class="nx">hexCode</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">          <span class="nx">onPress</span><span class="o">=</span><span class="p">{()</span> <span class="p">=&gt;</span> <span class="k">this</span><span class="p">.</span><span class="nx">props</span><span class="p">.</span><span class="nx">toggleTheme</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">props</span><span class="p">.</span><span class="nx">color</span><span class="p">)}</span>
</span></span><span class="line"><span class="cl">        <span class="o">/&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="o">&lt;</span><span class="err">/View&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">mapStateToProps</span> <span class="o">=</span> <span class="nx">state</span> <span class="p">=&gt;</span> <span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nx">color</span><span class="o">:</span> <span class="nx">state</span><span class="p">.</span><span class="nx">Theme</span><span class="p">.</span><span class="nx">colorData</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">mapDispatchToProps</span> <span class="o">=</span> <span class="nx">dispatch</span> <span class="p">=&gt;</span> <span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nx">toggleTheme</span><span class="o">:</span> <span class="nx">color</span> <span class="p">=&gt;</span> <span class="nx">dispatch</span><span class="p">(</span><span class="nx">toggleTheme</span><span class="p">(</span><span class="nx">color</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">connect</span><span class="p">(</span><span class="nx">mapStateToProps</span><span class="p">,</span> <span class="nx">mapDispatchToProps</span><span class="p">)(</span><span class="nx">ToggleTheme</span><span class="p">);</span>
</span></span></code></pre></div><p>This file does most of the heavy lifting for this app, this is where most of the logic sits. So, first of all, we have a button, which calls <code>toggleTheme</code> on a press of the and passes the current colour (state) as an argument.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">Button</span>
</span></span><span class="line"><span class="cl">  <span class="na">title</span><span class="o">=</span><span class="s">&#34;Toggle Color&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="na">color</span><span class="o">=</span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">props</span><span class="p">.</span><span class="nx">color</span><span class="p">.</span><span class="nx">hexCode</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="na">onPress</span><span class="o">=</span><span class="p">{()</span> <span class="p">=&gt;</span> <span class="k">this</span><span class="p">.</span><span class="nx">props</span><span class="p">.</span><span class="nx">toggleTheme</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">props</span><span class="p">.</span><span class="nx">color</span><span class="p">)}</span>
</span></span><span class="line"><span class="cl"><span class="p">/&gt;</span>
</span></span></code></pre></div><p>The colour of the button is also using the current (state) colour, so in this case, it will start off as red, <code>this.props.color</code>. We can access the current state using <code>this.props</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">mapStateToProps</span> <span class="o">=</span> <span class="p">(</span><span class="nx">state</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nx">color</span><span class="o">:</span> <span class="nx">state</span><span class="p">.</span><span class="nx">Theme</span><span class="p">.</span><span class="nx">colorData</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><p>This function is used to map the Redux store to the state of this component. We map <code>state.Theme.colorData</code>,
where <code>Theme</code> is the name of our reducer and <code>colorData</code> is the item name. In this example, we&rsquo;ve mapped the current <code>colorData</code> to <code>color</code> props in this component.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">mapDispatchToProps</span> <span class="o">=</span> <span class="p">(</span><span class="nx">dispatch</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nx">toggleTheme</span><span class="o">:</span> <span class="p">(</span><span class="nx">color</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="nx">dispatch</span><span class="p">(</span><span class="nx">toggleTheme</span><span class="p">(</span><span class="nx">color</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><p>This function maps a function to an action (and dispatches it), in this example when the <code>toggleTheme</code> function is called in the component it will dispatch the <code>toggleTheme</code> action to redux. So when the
button is pressed it will dispatch an action to toggle the theme.</p>
<h3 id="pagea">PageA</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-jsx" data-lang="jsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span><span class="p">,</span> <span class="p">{</span> <span class="nx">Component</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;react&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">View</span><span class="p">,</span> <span class="nx">Text</span> <span class="p">}</span> <span class="nx">from</span> <span class="s1">&#39;react-native&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">ToggleTheme</span> <span class="nx">from</span> <span class="s1">&#39;../components/ToggleTheme&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="kr">class</span> <span class="nx">PageA</span> <span class="kr">extends</span> <span class="nx">Component</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">render</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;</span><span class="nt">View</span> <span class="na">style</span><span class="o">=</span><span class="p">{{</span> <span class="nx">flex</span><span class="o">:</span> <span class="mi">1</span> <span class="p">}}&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">ToggleTheme</span> <span class="p">/&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">Text</span><span class="p">&gt;</span><span class="nx">PageA</span><span class="p">&lt;/</span><span class="nt">Text</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">&lt;/</span><span class="nt">View</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This is an example of one of our screens, it&rsquo;s very simple just the page name and <code>&lt;ToggleTheme&gt;</code> component.</p>
<h2 id="example">Example</h2>
<p><img
        loading="lazy"
        src="/posts/2018-12-23-theme-your-expo-react-native-app-with-redux-and-react-navigation/images/example.gif"
        type=""
        alt="example"
        
      /></p>
<p>Here is an example of the app running, as you can see there are two ways we can toggle the theme. We
could use the button click, or change tabs.</p>
<p><em>Extra Task</em>: If you want some practice, currently this won&rsquo;t detect a swipe action to change tabs
look at how you can do this. An example of it working can be found <a href="https://gitlab.com/hmajid2301/stegappasaurus/blob/development/src/screens/Decoding.js">here</a>.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2018-12-23-theme-your-expo-react-native-app-with-redux-and-react-navigation/source_code">Example source code</a></li>
<li><a href="https://reactnavigation.org/">React Navigation</a></li>
<li><a href="https://css-tricks.com/learning-react-redux/">Redux Animation</a></li>
<li><a href="http://slides.com/jenyaterpil/redux-from-twitter-hype-to-production#">Redux Diagram</a></li>
<li><a href="https://redux.js.org/">Redux</a></li>
<li>GIFs created with <a href="https://www.screentogif.com/">screentogif</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Testing a Flask App with pytest-mock and pytest-flask</title>
      <link>https://haseebmajid.dev/posts/2018-12-15-testing-a-flask-app-with-pytest-mock-and-pytest-flask/</link>
      <pubDate>Sat, 15 Dec 2018 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2018-12-15-testing-a-flask-app-with-pytest-mock-and-pytest-flask/</guid>
      <description>&lt;p&gt;Pytest is a popular Python library used for testing. It is my preferred testing library because it requires less boilerplate code than the alternatives such as (the builtin) unittest, the built in testing library.
In this article, I will show you how you can use &lt;code&gt;pytest-flask&lt;/code&gt; and &lt;code&gt;pytest-mock&lt;/code&gt; to test your Flask app. These two
libraries are plugins for Pytest which build upon some of the features that Pytest provides us.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Pytest is a popular Python library used for testing. It is my preferred testing library because it requires less boilerplate code than the alternatives such as (the builtin) unittest, the built in testing library.
In this article, I will show you how you can use <code>pytest-flask</code> and <code>pytest-mock</code> to test your Flask app. These two
libraries are plugins for Pytest which build upon some of the features that Pytest provides us.</p>
<p>In this example, we will be testing a very simple Flask app I created <a href="https://gitlab.com/hmajid2301/articles/-/tree/master/9.%20Testing%20with%20pytest-mock%20and%20pytest-flask/source_code">source code here</a>.
If you want to learn more about the Flask app you can read my previous article <a href="/posts/2018-11-24-building-a-simple-flask-app-with-sqlalchemy-and-docker/">here</a> :plug: :plug:.
All you really need to know for this article is that it involves using a very simple RESTful API for an imaginary cat store. Essentially all the API does is it interacts with a database,
to get current cats, add new cats, edit already existing cats and remove cats from the store.</p>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>Your own Flask app</li>
<li>Install the following dependencies, using <code>pip install -r requirements.txt</code> (or pip3 instead of pip)</li>
</ul>
<p>Where requirements.txt is:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">attrs==19.3.0
</span></span><span class="line"><span class="cl">click==7.1.2
</span></span><span class="line"><span class="cl">Flask==1.1.2
</span></span><span class="line"><span class="cl">Flask-SQLAlchemy==2.4.3
</span></span><span class="line"><span class="cl">itsdangerous==1.1.0
</span></span><span class="line"><span class="cl">Jinja2==2.11.2
</span></span><span class="line"><span class="cl">MarkupSafe==1.1.1
</span></span><span class="line"><span class="cl">more-itertools==8.4.0
</span></span><span class="line"><span class="cl">packaging==20.4
</span></span><span class="line"><span class="cl">pluggy==0.13.1
</span></span><span class="line"><span class="cl">psycopg2==2.8.5
</span></span><span class="line"><span class="cl">py==1.8.2
</span></span><span class="line"><span class="cl">pyparsing==2.4.7
</span></span><span class="line"><span class="cl">pytest==5.4.3
</span></span><span class="line"><span class="cl">pytest-flask==1.0.0
</span></span><span class="line"><span class="cl">pytest-mock==3.1.1
</span></span><span class="line"><span class="cl">six==1.15.0
</span></span><span class="line"><span class="cl">SQLAlchemy==1.3.17
</span></span><span class="line"><span class="cl">wcwidth==0.2.4
</span></span><span class="line"><span class="cl">Werkzeug==1.0.1
</span></span></code></pre></div><h2 id="libraries">Libraries</h2>
<p><code>pytest-flask</code>: Allows us to specify an app fixture and then send API requests with this app. Usage is similar to <code>requests</code> library, when sending HTTP requests to our flask app.</p>
<p><code>pytest-mock</code>: Is a simple wrapper around the unittest mock library, so anything you can do using <code>unittest.mock</code> you can do with <code>pytest-mock</code>. The main difference in usage is you can access it using a fixture <code>mocker</code>, also the mock ends at the end of the test. Whereas with the normal mock library if you mock say the <code>open()</code> function, it will be mocked for the remaining duration of that test module, i.e. it will effect other tests.</p>
<h2 id="pytest-flask-example">pytest-flask example</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pytest</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">example_app</span> <span class="kn">import</span> <span class="n">create_app</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">app</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">app</span> <span class="o">=</span> <span class="n">create_app</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">app</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">test_example</span><span class="p">(</span><span class="n">client</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;/&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span>
</span></span></code></pre></div><p>To use pytest-flask we need to create a fixture called <code>app()</code> which creates our Flask server. We can then use this fixture by passing <code>client</code>
as an argument to any test. Then we can send various http requests using <code>client</code>.</p>
<p>Above is a very simple example using pytest-flask, we send a GET request to our app, which should return all cats in the database.
We then check that the status code returned from the server was a 200 (OK). This is great except how can we mock out certain features within our code?</p>
<h2 id="pytest-mock">pytest-mock</h2>
<p>We can mock out certain parts of our code using the <code>pytest-mock</code> library, but we have to mock inside the <code>app()</code> fixture. Since the rest of our tests will just be making HTTP requests to our Flask server. In this example say we don&rsquo;t want to mock a connection to the database, we can use the following lines of code.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">mocker</span><span class="o">.</span><span class="n">patch</span><span class="p">(</span><span class="s2">&#34;flask_sqlalchemy.SQLAlchemy.init_app&#34;</span><span class="p">,</span> <span class="n">return_value</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">mocker</span><span class="o">.</span><span class="n">patch</span><span class="p">(</span><span class="s2">&#34;flask_sqlalchemy.SQLAlchemy.create_all&#34;</span><span class="p">,</span> <span class="n">return_value</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">mocker</span><span class="o">.</span><span class="n">patch</span><span class="p">(</span><span class="s2">&#34;example.database.get_all&#34;</span><span class="p">,</span> <span class="n">return_value</span><span class="o">=</span><span class="p">{})</span>
</span></span></code></pre></div><p>What this bit of code is doing is any time the any of the mocked functions are called, say <code>init_app</code> we mock it so it will always return true. We have to give it the &ldquo;full path&rdquo; to the function, it&rsquo;s the same as if you had to import the function itself.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pytest</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">example.app</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">app</span><span class="p">(</span><span class="n">mocker</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">mocker</span><span class="o">.</span><span class="n">patch</span><span class="p">(</span><span class="s2">&#34;flask_sqlalchemy.SQLAlchemy.init_app&#34;</span><span class="p">,</span> <span class="n">return_value</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">mocker</span><span class="o">.</span><span class="n">patch</span><span class="p">(</span><span class="s2">&#34;flask_sqlalchemy.SQLAlchemy.create_all&#34;</span><span class="p">,</span> <span class="n">return_value</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">mocker</span><span class="o">.</span><span class="n">patch</span><span class="p">(</span><span class="s2">&#34;example.database.get_all&#34;</span><span class="p">,</span> <span class="n">return_value</span><span class="o">=</span><span class="p">{})</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">example</span><span class="o">.</span><span class="n">app</span><span class="o">.</span><span class="n">app</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">test_example</span><span class="p">(</span><span class="n">client</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;/&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span>
</span></span></code></pre></div><p>In this example it&rsquo;s very boring as when we send an HTTP GET request to the app it will not interact with the database since we&rsquo;ve mocked this out but instead just return an empty dict ({}). In reality, this is
not a very good test, you would make it a bit more interesting.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">virtualenv .venv
</span></span><span class="line"><span class="cl"><span class="nb">source</span> .venv/bin/activate
</span></span><span class="line"><span class="cl">pip install -e .
</span></span><span class="line"><span class="cl">pip install -r requirements.txt
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="k">$(</span>xargs &lt; database.conf<span class="k">)</span>
</span></span><span class="line"><span class="cl">pytest
</span></span></code></pre></div><h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2018-12-15-testing-a-flask-app-with-pytest-mock-and-pytest-flask/source_code">Example source code</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Building A Simple Flask App with SQLalchemy and Docker</title>
      <link>https://haseebmajid.dev/posts/2018-11-24-building-a-simple-flask-app-with-sqlalchemy-and-docker/</link>
      <pubDate>Sat, 24 Nov 2018 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2018-11-24-building-a-simple-flask-app-with-sqlalchemy-and-docker/</guid>
      <description>&lt;p&gt;SQLAlchemy is an object-relational mapper (ORM), it allow us to interact with a database using Python functions and objects. For example, if we have a table called
&lt;code&gt;Cats&lt;/code&gt; we could retrieve every row with a command like &lt;code&gt;Cats.query.all()&lt;/code&gt;. The main advantage of this is that it allows us to abstract away the SQL.&lt;/p&gt;
&lt;p&gt;Docker &amp;#x1f433; allows us to quickly bring up a database within a Docker container, this means we don&amp;rsquo;t have to set up and configure a database on our local machine. We can simply kill the Docker container when we are done with the database. In this article, I will show you how you can create a very simple RESTful API using Flask and SQLAlchemy, which will connect to a database running in a Docker container.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>SQLAlchemy is an object-relational mapper (ORM), it allow us to interact with a database using Python functions and objects. For example, if we have a table called
<code>Cats</code> we could retrieve every row with a command like <code>Cats.query.all()</code>. The main advantage of this is that it allows us to abstract away the SQL.</p>
<p>Docker &#x1f433; allows us to quickly bring up a database within a Docker container, this means we don&rsquo;t have to set up and configure a database on our local machine. We can simply kill the Docker container when we are done with the database. In this article, I will show you how you can create a very simple RESTful API using Flask and SQLAlchemy, which will connect to a database running in a Docker container.</p>
<p><strong>NOTE:</strong> Flask server will be running locally, not in a Docker container.</p>
<p>In this example, I will be using Postgres but it should be easy enough to use any other relational database, such as MySQL. I will also be using <code>flask-sqlalchemy</code>, which is a wrapper around <code>SQLAlchemy</code>, it simplifies our code and means we can use less boilerplate code.</p>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li><a href="https://docs.docker.com/install/">Install Docker</a></li>
<li>(optional) <a href="https://docs.docker.com/compose/install/">Install docker-compose</a></li>
<li>Install Python3.6</li>
<li>Install the following dependencies, using <code>pip install -r requirements.txt</code> (or pip3 instead of pip)</li>
</ul>
<p>Where requirements.txt is:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">flask==1.0.2
</span></span><span class="line"><span class="cl">flask-sqlalchemy==2.3.0
</span></span><span class="line"><span class="cl">psycopg2==2.7.6.1
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">flask</span> <span class="kn">import</span> <span class="n">Flask</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">.models</span> <span class="kn">import</span> <span class="n">db</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">.</span> <span class="kn">import</span> <span class="n">config</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">create_app</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">flask_app</span> <span class="o">=</span> <span class="n">Flask</span><span class="p">(</span><span class="vm">__name__</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">flask_app</span><span class="o">.</span><span class="n">config</span><span class="p">[</span><span class="s1">&#39;SQLALCHEMY_DATABASE_URI&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">DATABASE_CONNECTION_URI</span>
</span></span><span class="line"><span class="cl">    <span class="n">flask_app</span><span class="o">.</span><span class="n">config</span><span class="p">[</span><span class="s1">&#39;SQLALCHEMY_TRACK_MODIFICATIONS&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="cl">    <span class="n">flask_app</span><span class="o">.</span><span class="n">app_context</span><span class="p">()</span><span class="o">.</span><span class="n">push</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">db</span><span class="o">.</span><span class="n">init_app</span><span class="p">(</span><span class="n">flask_app</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">db</span><span class="o">.</span><span class="n">create_all</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">flask_app</span>
</span></span></code></pre></div><p>The init file has one function <code>create_app()</code>, which funnily enough creates our Flask app with this line <code>Flask(__name__)</code>. It then assigns a URI, from the <code>config.py</code> file, to the Flask app&rsquo;s configuration. This URI is used to connect to the Postgres database.</p>
<p>One important thing about this function is that we have to use Flask contexts. Since Flask can have multiple apps we have to specify which app we are using with SQLAlchemy, hence we push the context with our newly created app. Else we would see the following error, <a href="http://flask-sqlalchemy.pocoo.org/2.3/contexts/">more information here</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">No application found. Either work inside a view function or push an application context.
</span></span></code></pre></div><p>After pushing our context, we link our <code>db</code> to the Flask app with the following line <code>db.init_app(flask_app)</code>. We then create all of our tables (in the database) if they don&rsquo;t already exist, using <code>db.create_all()</code>. The tables are created using the classes defined in <code>models.py</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">user</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s1">&#39;POSTGRES_USER&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">password</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s1">&#39;POSTGRES_PASSWORD&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">host</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s1">&#39;POSTGRES_HOST&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">database</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s1">&#39;POSTGRES_DB&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">port</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s1">&#39;POSTGRES_PORT&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">DATABASE_CONNECTION_URI</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">&#39;postgresql+psycopg2://</span><span class="si">{</span><span class="n">user</span><span class="si">}</span><span class="s1">:</span><span class="si">{</span><span class="n">password</span><span class="si">}</span><span class="s1">@</span><span class="si">{</span><span class="n">host</span><span class="si">}</span><span class="s1">:</span><span class="si">{</span><span class="n">port</span><span class="si">}</span><span class="s1">/</span><span class="si">{</span><span class="n">database</span><span class="si">}</span><span class="s1">&#39;</span>
</span></span></code></pre></div><p>This module&rsquo;s only job at the moment is to generate this URI, but could easily be extended to add extra configuration variables if required.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">DATABASE_CONNECTION_URI</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">&#39;postgresql+psycopg2://</span><span class="si">{</span><span class="n">user</span><span class="si">}</span><span class="s1">:</span><span class="si">{</span><span class="n">password</span><span class="si">}</span><span class="s1">@</span><span class="si">{</span><span class="n">host</span><span class="si">}</span><span class="s1">:</span><span class="si">{</span><span class="n">port</span><span class="si">}</span><span class="s1">/</span><span class="si">{</span><span class="n">database</span><span class="si">}</span><span class="s1">&#39;</span>
</span></span></code></pre></div><p><strong>NOTE:</strong> F-strings used for formatting strings (as shown above) can only be used with Python3.6.</p>
<p>These are examples of the variables that need to get passed as environment variables to the Flask app.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">POSTGRES_USER=test
</span></span><span class="line"><span class="cl">POSTGRES_PASSWORD=password
</span></span><span class="line"><span class="cl">POSTGRES_HOST=localhost
</span></span><span class="line"><span class="cl">POSTGRES_PORT=5432
</span></span><span class="line"><span class="cl">POSTGRES_DB=example
</span></span></code></pre></div><p><strong>NOTE:</strong> If you&rsquo;re running the Flask app in a Docker container you will need to change the variable <code>POSTGRES_HOST=postgres</code>, (from localhost)
where <code>postgres</code> is the Docker container name we are connecting to.</p>
<p><strong>WARNING:</strong> Make sure these are the same values passed to the Flask app and the Postgres database.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">flask_sqlalchemy</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">db</span> <span class="o">=</span> <span class="n">flask_sqlalchemy</span><span class="o">.</span><span class="n">SQLAlchemy</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Cats</span><span class="p">(</span><span class="n">db</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">__tablename__</span> <span class="o">=</span> <span class="s1">&#39;cats&#39;</span>
</span></span><span class="line"><span class="cl">    <span class="nb">id</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">Column</span><span class="p">(</span><span class="n">db</span><span class="o">.</span><span class="n">Integer</span><span class="p">,</span> <span class="n">primary_key</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">name</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">Column</span><span class="p">(</span><span class="n">db</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="mi">100</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="n">price</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">Column</span><span class="p">(</span><span class="n">db</span><span class="o">.</span><span class="n">Integer</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">breed</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">Column</span><span class="p">(</span><span class="n">db</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="mi">100</span><span class="p">))</span>
</span></span></code></pre></div><p>This module defines our classes which then become tables within our database. For example, the class <code>Cats</code> (cats) is the table name and each attribute becomes a column in that table. So the <code>cats</code> table with have four columns id, name, price and breed.</p>
<p>The <code>db</code> variable is imported from here by the <code>__init__.py</code> file, that&rsquo;s how the <code>db.create_all()</code> function knows which classes/tables to create in the database.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">json</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">flask</span> <span class="kn">import</span> <span class="n">request</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">.</span> <span class="kn">import</span> <span class="n">create_app</span><span class="p">,</span> <span class="n">database</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">.models</span> <span class="kn">import</span> <span class="n">Cats</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">create_app</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@app.route</span><span class="p">(</span><span class="s1">&#39;/&#39;</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s1">&#39;GET&#39;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">fetch</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">cats</span> <span class="o">=</span> <span class="n">database</span><span class="o">.</span><span class="n">get_all</span><span class="p">(</span><span class="n">Cats</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">all_cats</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">cat</span> <span class="ow">in</span> <span class="n">cats</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">new_cat</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;id&#34;</span><span class="p">:</span> <span class="n">cat</span><span class="o">.</span><span class="n">id</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="n">cat</span><span class="o">.</span><span class="n">name</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;price&#34;</span><span class="p">:</span> <span class="n">cat</span><span class="o">.</span><span class="n">price</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;breed&#34;</span><span class="p">:</span> <span class="n">cat</span><span class="o">.</span><span class="n">breed</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">all_cats</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">new_cat</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">all_cats</span><span class="p">),</span> <span class="mi">200</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@app.route</span><span class="p">(</span><span class="s1">&#39;/add&#39;</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s1">&#39;POST&#39;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">add</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">get_json</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">name</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="s1">&#39;name&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">price</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="s1">&#39;price&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">breed</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="s1">&#39;breed&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">database</span><span class="o">.</span><span class="n">add_instance</span><span class="p">(</span><span class="n">Cats</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="n">name</span><span class="p">,</span> <span class="n">price</span><span class="o">=</span><span class="n">price</span><span class="p">,</span> <span class="n">breed</span><span class="o">=</span><span class="n">breed</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="s2">&#34;Added&#34;</span><span class="p">),</span> <span class="mi">200</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@app.route</span><span class="p">(</span><span class="s1">&#39;/remove/&lt;cat_id&gt;&#39;</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s1">&#39;DELETE&#39;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">remove</span><span class="p">(</span><span class="n">cat_id</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">database</span><span class="o">.</span><span class="n">delete_instance</span><span class="p">(</span><span class="n">Cats</span><span class="p">,</span> <span class="nb">id</span><span class="o">=</span><span class="n">cat_id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="s2">&#34;Deleted&#34;</span><span class="p">),</span> <span class="mi">200</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@app.route</span><span class="p">(</span><span class="s1">&#39;/edit/&lt;cat_id&gt;&#39;</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s1">&#39;PATCH&#39;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">edit</span><span class="p">(</span><span class="n">cat_id</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">get_json</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">new_price</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="s1">&#39;price&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">database</span><span class="o">.</span><span class="n">edit_instance</span><span class="p">(</span><span class="n">Cats</span><span class="p">,</span> <span class="nb">id</span><span class="o">=</span><span class="n">cat_id</span><span class="p">,</span> <span class="n">price</span><span class="o">=</span><span class="n">new_price</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="s2">&#34;Edited&#34;</span><span class="p">),</span> <span class="mi">200</span>
</span></span></code></pre></div><p>This is a simple Flask file, which creates our app by calling <code>create_app()</code> function from <code>__init__.py</code> module.
Then it defines four functions for our four routes for the &ldquo;RESTful&rdquo; API:</p>
<ul>
<li>GET: Get information about all the cats</li>
<li>POST: Add a new cat</li>
<li>DELETE: Remove a cat</li>
<li>PATCH: Edit a cat&rsquo;s price</li>
</ul>
<h2 id="databasepy">database.py</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">.models</span> <span class="kn">import</span> <span class="n">db</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_all</span><span class="p">(</span><span class="n">model</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span> <span class="o">=</span> <span class="n">model</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">data</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">add_instance</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">instance</span> <span class="o">=</span> <span class="n">model</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">db</span><span class="o">.</span><span class="n">session</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">instance</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">commit_changes</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">delete_instance</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="nb">id</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">model</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">filter_by</span><span class="p">(</span><span class="nb">id</span><span class="o">=</span><span class="nb">id</span><span class="p">)</span><span class="o">.</span><span class="n">delete</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">commit_changes</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">edit_instance</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="nb">id</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">instance</span> <span class="o">=</span> <span class="n">model</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">filter_by</span><span class="p">(</span><span class="nb">id</span><span class="o">=</span><span class="nb">id</span><span class="p">)</span><span class="o">.</span><span class="n">all</span><span class="p">()[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">attr</span><span class="p">,</span> <span class="n">new_value</span> <span class="ow">in</span> <span class="n">kwargs</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="nb">setattr</span><span class="p">(</span><span class="n">instance</span><span class="p">,</span> <span class="n">attr</span><span class="p">,</span> <span class="n">new_value</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">commit_changes</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">commit_changes</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">db</span><span class="o">.</span><span class="n">session</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
</span></span></code></pre></div><p>This module is created so we can abstract away how we interact with the database. We simply use
the functions in this module to interact with the database. This means it&rsquo;s easier to change the
library we use to interact with the database. It also means that if for some reason we need to change how we interact with the database. We only have to change it in a single module (this one).</p>
<p>The <code>app.py</code> module calls functions in this file to interact with database.</p>
<ul>
<li>GET - <code>get_all()</code></li>
<li>POST - <code>add_instance()</code></li>
<li>DELETE - <code>delete_instance()</code></li>
<li>PUT - <code>edit_instance()</code></li>
</ul>
<p>Some functions use this special keyword called <code>**kwargs</code>, kwargs (keyword arguments) could be called anything but it&rsquo;s best practice to call it kwargs. This allows
the caller of the function to pass in an arbitrary number of keyword arguments.</p>
<p>Let&rsquo;s take a look at the <code>add_instance()</code> function as an example. The function is called in <code>app.py</code> like so <code>database.add_instance(Cats, name=name, price=price, breed=breed)</code> the <code>model=Cats</code> and <code>kwargs</code> is the rest of the arguments which are passed onto the cats model so we can add our cat object to the database.</p>
<p><strong>NOTE:</strong> The <code>kwargs</code> just stores the arguments as a dictionary, the <code>**</code> operator unpacks our dictionary
and passes them as keyword arguments.</p>
<h2 id="docker-compose">Docker Compose</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;3.5&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">database</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">postgres</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">postgres:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">env_file</span><span class="p">:</span><span class="w"> </span><span class="l">database.conf</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">5432</span><span class="p">:</span><span class="m">5432</span><span class="w">  
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">db_volume:/var/lib/postgresql</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">db_volume</span><span class="p">:</span><span class="w">
</span></span></span></code></pre></div><p>For development, I like to use docker-compose. In docker compose we can specify Docker containers using YAML. It can help to simplify the commands we need to type when trying to build/run multiple Docker containers. In this example, we only define a single Docker container.</p>
<p>Taking a look at the file:</p>
<p>First, we define our version number <code>version: '3.5'</code>, it is recommended by Docker that you use at least version 3. You can find
<a href="https://docs.docker.com/compose/compose-file/compose-versioning/">more information here</a>.</p>
<p>Then we give our a service name, in this case, <code>database</code>. I like to name my services with what they are used for generic names such as <code>web server</code>, <code>database</code> or <code>message broker</code>. This means I can change the underlying technology without changing the service name.</p>
<p>After this we name our container <code>postgres</code>, this is the name of the Docker container.
It can be used to interact with the container (to kill it or exec onto it) without using an ID.</p>
<p>We use the official Postgres image on <a href="https://hub.docker.com/_/postgres/">Docker Hub</a>, we pull the image that is tagged with <code>latest</code>.</p>
<p>This image requires us to use some variables to set it up such as username, password and database. We pass these in the form of a file to make things a bit simpler (the same `database.conf as defined above).</p>
<p>We then map the host port 5432 to the guest Docker container port 5432, this is the port that Postgres listens on. You could change the host port if you wanted to something else like <code>9000</code> say, this means all traffic on the host on port 9000 will be sent to the Postgres container on port 5432. We would also need to update the environment variable the Flask app is using.</p>
<p>Finally, we mount a volume so that our data is persistent, without this when the database Docker container is killed you would lose all of your data. By mounting <code>db_volume</code> even when you kill the container, like when you want to update the Postgres image, your data will persist.</p>
<h2 id="running-our-application">Running our application</h2>
<p>To build and run our Docker container with docker-compose:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker-compose up --build -d
</span></span></code></pre></div><p>The equivalent commands using just normal Docker would be</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker volume create --name db_volume
</span></span><span class="line"><span class="cl">docker run -d --name postgres -p 5432:5432 <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>           --env-file docker/database.conf <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>           -v db_volume:/var/lib/postgresql postgres:latest
</span></span></code></pre></div><p>To start our Flask app</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker-compose up --build
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># In a new terminal</span>
</span></span><span class="line"><span class="cl">virtualenv .venv
</span></span><span class="line"><span class="cl"><span class="nb">source</span> .venv/bin/activate
</span></span><span class="line"><span class="cl">pip install -r requirements.txt
</span></span><span class="line"><span class="cl"><span class="c1"># To load env variables</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="k">$(</span>xargs &lt; database.conf<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">FLASK_APP</span><span class="o">=</span>src/example/app.py
</span></span><span class="line"><span class="cl">flask run
</span></span><span class="line"><span class="cl"><span class="c1"># Running on http://127.0.0.1:5000</span>
</span></span></code></pre></div><p>You can send HTTP requests to your Flask server on <code>127.0.0.1:5000</code>, you can either use a REST client like Postman or Insomnia. You can also use cURL on the cli.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -XPOST -H <span class="s2">&#34;Content-type: application/json&#34;</span> -d <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span><span class="s1">&#39;{&#34;name&#34;: &#34;catty mcCatFace&#34;, &#34;price&#34;: 5000, &#34;breed&#34;: &#34;bengal&#34;}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span><span class="s1">&#39;127.0.0.1:5000/add&#39;</span>
</span></span></code></pre></div><h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2018-11-24-building-a-simple-flask-app-with-sqlalchemy-and-docker/source_code">Example source code</a></li>
<li><a href="https://www.getpostman.com/">Postman</a></li>
<li><a href="https://insomnia.rest/">Insomnia</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Using Multiple Docker Containers to Setup Nginx, Flask and Postgres</title>
      <link>https://haseebmajid.dev/posts/2018-11-19-using-multiple-docker-containers-to-setup-nginx-flask-and-postgres/</link>
      <pubDate>Mon, 19 Nov 2018 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2018-11-19-using-multiple-docker-containers-to-setup-nginx-flask-and-postgres/</guid>
      <description>&lt;h2 id=&#34;terminology&#34;&gt;Terminology&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Docker Image: Is a file used to execute code in a Docker container, built from a set of instructions.&lt;/li&gt;
&lt;li&gt;Docker Container: Is a Docker image that is being executed or run.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Docker &amp;#x1f433; is a relatively new and exciting technology, Docker is a containerisation tool. The main benefits of using Docker is that you can
use the same environment for development, testing and production. Since Docker environments are consistent this means if the application works
in the testing environment it will also work in production.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="terminology">Terminology</h2>
<ul>
<li>Docker Image: Is a file used to execute code in a Docker container, built from a set of instructions.</li>
<li>Docker Container: Is a Docker image that is being executed or run.</li>
</ul>
<p>Docker &#x1f433; is a relatively new and exciting technology, Docker is a containerisation tool. The main benefits of using Docker is that you can
use the same environment for development, testing and production. Since Docker environments are consistent this means if the application works
in the testing environment it will also work in production.</p>
<p>Another big advantage of Docker is that I don&rsquo;t need to download all the dependencies on my own machine directly. To build an entire application and run it all I need is a Dockerfile so if I&rsquo;m the go a lot and using lots of different machines, I can easily set up my own development environment simply by using Docker. All Docker does it execute a set of instructions so since the instructions are the same, the environment Docker will create (Docker container) will also be the same.</p>
<p>In this tutorial, I will show you how to set up a Python &#x1f40d; application using multiple Docker containers. In theory, you could have one big Docker container
that has Nginx, Flask and Postgres but I prefer to split the application up. For example into its core components, web server (Nginx), application (Flask) and
database (Postgres). The main advantage of this is that it makes it easier to replace components of the application and also makes it easier to detect errors
as you can see which container is cauing the error.</p>
<p><strong>Note</strong>: Everything in this tutorial has been tested on Ubuntu Linux.</p>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li><a href="https://docs.docker.com/install/">Install Docker</a></li>
<li>(optional) <a href="https://docs.docker.com/compose/install/">Install docker-compose</a></li>
</ul>
<h2 id="nginx">Nginx</h2>
<p>The first Docker container called Nginx will be the main gateway into our application it will be used as a proxy server. It will receive HTTP requests and forward them onto our Python application.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dockerfile" data-lang="dockerfile"><span class="line"><span class="cl"><span class="k">FROM</span><span class="s"> nginx:latest</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">RUN</span> rm /etc/nginx/conf.d/default.conf<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">COPY</span> docker/nginx/example.conf /etc/nginx/conf.d/<span class="err">
</span></span></span></code></pre></div><p>This is a very simple dockerfile that takes uses the latest Nginx docker image. It then removes the default configuration and adds our configuration.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">server {
</span></span><span class="line"><span class="cl">  listen 80;
</span></span><span class="line"><span class="cl">  server_name _;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  location / {
</span></span><span class="line"><span class="cl">    try_files $uri @app;
</span></span><span class="line"><span class="cl">  }
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  location @app {
</span></span><span class="line"><span class="cl">    include /etc/nginx/uwsgi_params;
</span></span><span class="line"><span class="cl">    uwsgi_pass flask:8080;
</span></span><span class="line"><span class="cl">  }
</span></span><span class="line"><span class="cl">}
</span></span></code></pre></div><p>This is a simple Nginx configuration file which listens for traffic on port 80 (HTTP). It then passes on the data to uWSGI (hence <code>location /</code>). We then pass the HTTP request to another Docker container called <code>flask</code> on port 8080. This configuration cannot be used for https. <strong>Warning</strong> Only use https to send secure data. The reason we give it the container name <code>flask</code> rather than <code>localhost</code> is because this is how Docker networking works by default (bridge networking) to allow container to container communication. We do a something thing to allow when connecting Flask container to the Postgres container.</p>
<h2 id="flask">Flask</h2>
<blockquote>
<p>NOTE: Link to the <a href="https://gitlab.com/hmajid2301/articles/-/tree/master/7.%20Multi%20Docker%20Container%20with%20Nginx%2C%20Flask%20and%C2%A0MySQL/source_code">Python app source code</a> in <code>source_code/src/example/</code> this is the code that is turned into the <code>tar</code> in the <code>dist</code> folder.</p>
</blockquote>
<p>The second Docker container will contain our Python application running on a uWSGI server. The uWSGI server is a web application server based on the WSGI specification will allow Python to communicate with web servers. In this case, it essentially acts as middleware between Nginx and Flask translating requests between them. So essentially uWSGI receives an HTTP request from Nginx and translates into something Flask can understand. This container stores all the core Python code required for this simple API.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dockerfile" data-lang="dockerfile"><span class="line"><span class="cl"><span class="c"># Base Image</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">FROM</span><span class="s"> python:3.6-alpine as BASE</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">RUN</span> apk add --no-cache linux-headers g++ postgresql-dev gcc build-base linux-headers ca-certificates python3-dev libffi-dev libressl-dev libxslt-dev<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">RUN</span> pip wheel --wheel-dir<span class="o">=</span>/root/wheels psycopg2<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">RUN</span> pip wheel --wheel-dir<span class="o">=</span>/root/wheels cryptography<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="c"># Actual Image</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">FROM</span><span class="s"> python:3.6-alpine as RELEASE</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">EXPOSE</span><span class="s"> 8080</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">WORKDIR</span><span class="s"> /app </span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">ENV</span> <span class="nv">POSTGRES_USER</span><span class="o">=</span><span class="s2">&#34;&#34;</span> <span class="nv">POSTGRES_PASSWORD</span><span class="o">=</span><span class="s2">&#34;&#34;</span> <span class="nv">POSTGRES_HOST</span><span class="o">=</span>postgres <span class="nv">POSTGRES_PORT</span><span class="o">=</span><span class="m">5432</span> <span class="nv">POSTGRES_DB</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">COPY</span> dist/ ./dist/<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">COPY</span> docker/flask/uwsgi.ini ./<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">COPY</span> --from<span class="o">=</span>BASE /root/wheels /root/wheels<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">RUN</span> apk add --no-cache build-base linux-headers postgresql-dev pcre-dev libpq uwsgi-python3 <span class="o">&amp;&amp;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    pip install --no-index --find-links<span class="o">=</span>/root/wheels /root/wheels/* <span class="o">&amp;&amp;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    pip install dist/*<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">CMD</span> <span class="p">[</span><span class="s2">&#34;uwsgi&#34;</span><span class="p">,</span> <span class="s2">&#34;--ini&#34;</span><span class="p">,</span> <span class="s2">&#34;/app/uwsgi.ini&#34;</span><span class="p">]</span><span class="err">
</span></span></span></code></pre></div><p>This dockerfile uses a relatively new Docker feature called multi-stage builds. Here we use a base image (Python3.6) to generate some Python wheel files. These wheel files require specific Linux dependencies that we don&rsquo;t actually need in our main Docker container. Then we define our actual image and copy over the wheel files that we need for our application. This is done to help reduce the size of our Docker image file, we want to try to make the image as possible (as much as it makes sense). At the end of the dockerfile, the <code>BASE</code> image is destroyed.</p>
<p>So our actual Docker image is a bit more interesting. It does the following;</p>
<ul>
<li>It exposes port 8080, this actually doesn&rsquo;t do anything but is simply for documentation purposes</li>
<li>It then creates a default directory <code>/app/</code></li>
<li>We define some environment variables that are required by this container (all used to connect to Postgres). <strong>Please Note</strong> in a production environment you don&rsquo;t want to expose passwords and username as environment variables on your docker containers, instead, you should use a secrets stores such as <a href="https://www.vaultproject.io/">HashiCorp Vault</a>. These variables will be defined when we run the container</li>
<li>It copies the <code>dist</code> folder which has our Python package as a tar file. You can generate this file if you have a <code>setup.py</code> file and run <code>python setup.py sdist</code> in the same folder as your <code>setup.py</code></li>
<li>It copies the <code>uwsgi.ini</code> used to configure the uWSGI server</li>
<li>It copies all the wheels folder from the <code>BASE</code> image hence the <code>--from=BASE</code></li>
<li>Then we install the wheels and our everything in the <code>dist</code> folder, which our Python code as a <code>tar</code></li>
<li>Finally when the Docker image will be run it will execute <code>uwsgi --ini /app/uwsgi.ini</code>. Using the uwsgi.ini file we copied into the image</li>
</ul>
<p>In theory, you could simply copy and install the requirements.txt and copy all the source code to the <code>/app</code> folder. However, I prefer to generate and install the actual Python package I think it&rsquo;s cleaner and you only have to copy a single <code>tar</code> file. However, this does require you to run the command to generate the <code>dist</code> folder before you try to build the Docker image.</p>
<p><strong>Note</strong>: The environment variables POSTGRES_ should be the same values as defined in database.conf.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[uwsgi]</span>
</span></span><span class="line"><span class="cl"><span class="na">socket</span> <span class="o">=</span> <span class="s">:8080</span>
</span></span><span class="line"><span class="cl"><span class="na">module</span> <span class="o">=</span> <span class="s">example.wsgi:app</span>
</span></span><span class="line"><span class="cl"><span class="na">master</span> <span class="o">=</span> <span class="s">1</span>
</span></span><span class="line"><span class="cl"><span class="na">processes</span> <span class="o">=</span> <span class="s">4</span>
</span></span><span class="line"><span class="cl"><span class="na">plugin</span> <span class="o">=</span> <span class="s">python</span>
</span></span></code></pre></div><p>This is the configuration file used by the uWSGI server. This is where we define which port uWSGI listens for traffic on in this cases it&rsquo;s 8080. <strong>Note</strong> that since we&rsquo;ve defined <code>socket</code> you cannot access the uWSGI server directly you need a web server in front of it, if you wanted to use just uWSGI you would change this option to <code>http</code>. The other import option is the <code>module</code>, we point it to our installed module example then the wsgi module and app variable. Hence the <code>module=example.wsgi:app</code>. In this example the <code>wsgi.py</code> module calls <code>create_app()</code> function which creates the Flask app.</p>
<h2 id="postgres">Postgres</h2>
<p>The Postgres image is simpler the latest Postgres image from Docker hub, then we pass some environment variables to it to configure it.</p>
<pre tabindex="0"><code class="language-conf" data-lang="conf">POSTGRES_USER=test
POSTGRES_PASSWORD=password
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
POSTGRES_DB=example
</code></pre><p>The environment variables passed look something like this.
<strong>NOTE</strong> You don&rsquo;t need to pass the port or the host to Postgres Docker container. These are used by the Flask container.</p>
<h2 id="docker-compose">Docker Compose</h2>
<p>So we&rsquo;ve defined our dockerfile and configuration files used by those dockerfiles but how do we actually use Docker. One way we can use docker is to define it using docker-compose. Here we define a set of services and Docker will automatically run and build those services and handle the networking for us. I personally use docker-compose for development as it saves a lot of time running the <code>docker build</code> and <code>docker run</code> commands for each Docker image/container.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;3.5&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">web_server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">nginx</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">build</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">context</span><span class="p">:</span><span class="w"> </span><span class="l">.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">dockerfile</span><span class="p">:</span><span class="w"> </span><span class="l">docker/nginx/Dockerfile</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">80</span><span class="p">:</span><span class="m">80</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">app</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">app</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">flask</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">build</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">context</span><span class="p">:</span><span class="w"> </span><span class="l">.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">dockerfile</span><span class="p">:</span><span class="w"> </span><span class="l">docker/flask/Dockerfile</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">env_file</span><span class="p">:</span><span class="w"> </span><span class="l">docker/database.conf</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">expose</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">8080</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">database</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">postgres</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">postgres:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">env_file</span><span class="p">:</span><span class="w"> </span><span class="l">docker/database.conf</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">5432</span><span class="p">:</span><span class="m">5432</span><span class="w">  
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">db_volume:/var/lib/postgresql</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">db_volume</span><span class="p">:</span><span class="w">
</span></span></span></code></pre></div><p>So at the top of the file we define the version number of docker-compose, it is recommended that users use version 3 now. Then we define our services, one service equals one Docker container. Each service is given a name, such as web_server, app and database.</p>
<h3 id="web_server">web_server</h3>
<p>This service runs our Nginx server. We call the container <code>nginx</code> for obvious reasons. We pass it the location of the dockerfile to build. The build <code>context: .</code> simply means relative to this current working directory so we when we COPY file we will copy them relative to docker-compose.yml file. The final thing we do publish our ports so all traffic on the host machine on port 80 is mapped to the Docker container also on port 80. We could use any port on our guest machine say we used 8001, then we would access the web server by going to <code>localhost:8001</code>. The final part specifies this container depends on the app container so app will be run before this container.</p>
<h3 id="app">app</h3>
<p>This is a relatively simple service, again we point it to our flask dockerfile and set build context to the current folder. Then we pass some environment variables as a file, the variables are taken from this file (same <code>database.conf</code> as defined above). These variables are used to allow Python to connect to the database. Finally, we expose port 8080, again this for documentation purposes so other users know this container expects traffic on port 8080. Since we need to connect to the database when we set up our app we depend on the database container being run first.</p>
<h3 id="database">database</h3>
<p>This service doesn&rsquo;t have its own dockerfile but instead uses the official Postgres image. It then passes some environment variables as a file, the same file that gets passed to Flask container. We don&rsquo;t actually need the host or port variables but it&rsquo;s easier to maintain a single file in this case. We then map the host port 5432 to the guest Docker container port 5432. This is the port that Postgres listens on. Like with Nginx you can set the host port to whatever you want, but make sure you change this in <code>database.conf</code> and update <code>POSTGRES_PORT</code> variable. Finally, we mount a volume so that data is persistent, without this when the database Docker container was killed you would lose all your data. By mounting <code>db_volume</code> even you kill the container to say update the Postgres image your data will persist.</p>
<h3 id="docker-compose-buildrun">Docker Compose Build/Run</h3>
<p>To actually run the docker-compose file (in the same folder as <code>docker-compose.yml</code>), you can do something like below. Where <code>-d</code> means it runs in the background. This will build all three services and once it has built our Docker images it will run those Docker images as Docker containers.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker-compose up --build -d
</span></span></code></pre></div><h3 id="docker-buildrun">Docker Build/Run</h3>
<p>One important thing to note is <code>docker-compose</code> should not really be used in production for several reasons, such as downtime when updating your Docker containers. If your deploying to only 1 host docker-compose should be fine but in reality, most applications required high availability and zero downtime updates, in this case, you should at using a container orchestration tools such as Kuberenetes. So an alternative approach is to build and run each Docker container yourself, the equivalent commands for this <code>docker-compose.yml</code> file would be.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Build our images first</span>
</span></span><span class="line"><span class="cl">docker build -f docker/nginx/Dockerfile -t nginx .
</span></span><span class="line"><span class="cl">docker build -f docker/flask/Dockerfile -t flask .
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Run our containers</span>
</span></span><span class="line"><span class="cl">docker run -d --name nginx -p 80:80 nginx
</span></span><span class="line"><span class="cl">docker run -d --name flask -p <span class="m">8080</span>  --env-file docker/database.conf flask
</span></span><span class="line"><span class="cl">docker volume create --name db_volume
</span></span><span class="line"><span class="cl">docker run -d --name postgres -p 5432:5432 <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>           ---------------------------------------------------------------------------------------------------env-file docker/database.conf <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>           -v db_volume:/var/lib/postgresql postgres:latest
</span></span></code></pre></div><p><strong>Note</strong>: After you&rsquo;ve built your own images you can push them to either a public or private Docker registry so you or other people can access them. This is a common way to access your images during your CI pipeline. In fact base images like <code>postgres:latest</code> are taken from the <a href="https://hub.docker.com/_/postgres/">official Docker registry</a>.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2018-11-19-using-multiple-docker-containers-to-setup-nginx-flask-and-postgres/source_code">Example source code</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Pytest with Background Thread Fixtures</title>
      <link>https://haseebmajid.dev/posts/2018-11-05-pytest-with-background-thread-fixtures/</link>
      <pubDate>Mon, 05 Nov 2018 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2018-11-05-pytest-with-background-thread-fixtures/</guid>
      <description>&lt;p&gt;Recently I had to test some of my Python &amp;#x1f40d; &amp;#x1f40d; &amp;#x1f40d; code which required an external dependency and communicating by using TCP sockets &amp;#x1f50c; . You can think of this dependency as essentially a database because it stored information. However, when testing my Python code, I couldn&amp;rsquo;t rely on there always being a TCP server to send messages to.&lt;/p&gt;
&lt;p&gt;So I ended up creating a simplified mocked version in Python. This way I could run automated tests on my code without needing to either install/rely on this server already existing there. Essentially I ended up creating a TCP server in Python, which would receive some input and respond how I would expect the real TCP server too.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Recently I had to test some of my Python &#x1f40d; &#x1f40d; &#x1f40d; code which required an external dependency and communicating by using TCP sockets &#x1f50c; . You can think of this dependency as essentially a database because it stored information. However, when testing my Python code, I couldn&rsquo;t rely on there always being a TCP server to send messages to.</p>
<p>So I ended up creating a simplified mocked version in Python. This way I could run automated tests on my code without needing to either install/rely on this server already existing there. Essentially I ended up creating a TCP server in Python, which would receive some input and respond how I would expect the real TCP server too.</p>
<p>I write my tests using Pytest for several reasons, I prefer it to unittest because it required less boilerplate code. It also has lots of useful features such as fixtures. However, when creating tests for my API I realised that I had to first start the fake TCP server. But since it was always listening &#x1f442; for traffic &#x1f6a6; it would block the rest of my tests from running. So in this example, I will show you how you can create a Pytest fixture and run it on a background thread (TCP server) and still run your tests as normal.</p>
<h2 id="code">Code</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">socket</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">TCPServer</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_sock</span> <span class="o">=</span> <span class="n">socket</span><span class="o">.</span><span class="n">socket</span><span class="p">(</span><span class="n">socket</span><span class="o">.</span><span class="n">AF_INET</span><span class="p">,</span> 
</span></span><span class="line"><span class="cl">                                       <span class="n">socket</span><span class="o">.</span><span class="n">SOCK_STREAM</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_sock</span><span class="o">.</span><span class="n">setsockopt</span><span class="p">(</span><span class="n">socket</span><span class="o">.</span><span class="n">SOL_SOCKET</span><span class="p">,</span> 
</span></span><span class="line"><span class="cl">                                  <span class="n">socket</span><span class="o">.</span><span class="n">SO_REUSEADDR</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="fm">__enter__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_sock</span><span class="o">.</span><span class="n">bind</span><span class="p">((</span><span class="s1">&#39;127.0.0.1&#39;</span><span class="p">,</span> <span class="mi">9500</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="bp">self</span>
</span></span><span class="line"><span class="cl">            
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="fm">__exit__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">exception_type</span><span class="p">,</span> <span class="n">exception_value</span><span class="p">,</span> <span class="n">traceback</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">_sock</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">            
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">listen_for_traffic</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">            <span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="bp">self</span><span class="o">.</span><span class="n">_sock</span><span class="o">.</span><span class="n">listen</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="n">connection</span><span class="p">,</span> <span class="n">address</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_sock</span><span class="o">.</span><span class="n">accept</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">                <span class="n">message</span> <span class="o">=</span> <span class="n">connection</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">1024</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="n">response</span> <span class="o">=</span> <span class="s2">&#34;Received&#34;</span>
</span></span><span class="line"><span class="cl">                <span class="n">connection</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">encode</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">                <span class="n">connection</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
</span></span></code></pre></div><p>This is a very basic TCP server, it received some input but always replies with &ldquo;Received&rdquo;. Also, I created the class to be used with the context manager so that the socket can be closed, hence the <em>__enter__()</em> and <em>__exit__()</em> dunder (magic) method. This TCP server will always listen on localhost on port 9500, this is the same port the client will also have to connect to, to send messages to this TCP server.</p>
<p>The main function of interest is <em>listen_for_traffic()</em>, which loops forever whilst listening for traffic. If it receives any data, it always replies with &ldquo;Received&rdquo;, of course with a real server we might want to do something more interesting but to keep this example simple that&rsquo;s all this server will do.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">socket</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">threading</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pytest</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">.tcp_server</span> <span class="kn">import</span> <span class="n">TCPServer</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span><span class="p">(</span><span class="n">autouse</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">dummy_tcp_server</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">tcp_server</span> <span class="o">=</span> <span class="n">TCPServer</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="n">example_server</span> <span class="k">as</span> <span class="n">tcp_server</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">thread</span> <span class="o">=</span> <span class="n">threading</span><span class="o">.</span><span class="n">Thread</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">example_server</span><span class="o">.</span><span class="n">listen_for_traffic</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">thread</span><span class="o">.</span><span class="n">daemon</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="cl">        <span class="n">thread</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="k">yield</span> <span class="n">example_server</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">test_example</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">HOST</span> <span class="o">=</span> <span class="s1">&#39;127.0.0.1&#39;</span>
</span></span><span class="line"><span class="cl">    <span class="n">PORT</span> <span class="o">=</span> <span class="mi">9500</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">data</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="n">socket</span><span class="o">.</span><span class="n">socket</span><span class="p">(</span><span class="n">socket</span><span class="o">.</span><span class="n">AF_INET</span><span class="p">,</span> <span class="n">socket</span><span class="o">.</span><span class="n">SOCK_STREAM</span><span class="p">)</span> <span class="k">as</span> <span class="n">s</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">s</span><span class="o">.</span><span class="n">connect</span><span class="p">((</span><span class="n">HOST</span><span class="p">,</span> <span class="n">PORT</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="n">s</span><span class="o">.</span><span class="n">sendall</span><span class="p">(</span><span class="sa">b</span><span class="s1">&#39;Hello, world&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">data</span> <span class="o">=</span> <span class="n">s</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">1024</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="n">data</span><span class="o">.</span><span class="n">decode</span><span class="p">()</span> <span class="o">==</span> <span class="s2">&#34;Received&#34;</span>
</span></span></code></pre></div><p>When you run the Pytest command it will automatically run tests on all modules and method that start with &ldquo;test_&rdquo;. I have created a fixture (using the fixture decorator), fixtures allow for code reuse within a Pytest module. Fixtures are typically used to connect to databases, fixtures are the run before any tests hence we can also use them to setup is code.</p>
<p>So taking a look at the <em>dummy_tcp_server()</em> function, first we use a context manager (with) to set up our TCP server.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">with</span> <span class="n">tcp_server</span> <span class="k">as</span> <span class="n">example_server</span><span class="p">:</span>
</span></span></code></pre></div><p>This calls the <em>__enter__()</em> dunder method, which starts up our TCP server and so it&rsquo;s now listening for any traffic on port 9500.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">thread</span> <span class="o">=</span> <span class="n">threading</span><span class="o">.</span><span class="n">Thread</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">example_server</span><span class="o">.</span><span class="n">listen_for_traffic</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">thread</span><span class="o">.</span><span class="n">daemon</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="cl"><span class="n">thread</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span></code></pre></div><p>This is the bit of code that actually runs our TCP server on another (background) thread. So the in the first line we tell the thread what function to call, <em>listen_for_traffic</em> (without brackets) has an infinite while loops just waiting to listen for traffic arriving on port 9500. We set the thread as a daemon so that when we close the main Python program the thread will automatically kill itself, this means we don&rsquo;t have to manage it yourself.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">yield</span> <span class="n">example_server</span>
</span></span></code></pre></div><p>The yield is very similar to a return statement. The main reason I choose to include is that if the tests for some reason raise an exception, the yield will still allow the context manager to exit. If we had any tear down code after the yield even with an exception, this code will still run after all the tests have finished. Also yields (and return statements) allow individual tests to access functions from the TCPServer class. For example, if you had a function (in the TCPServer class) that stored every message sent to the server. If you pass the fixture as a parameter to any tests then you could access this function.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">test_example</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">HOST</span> <span class="o">=</span> <span class="s1">&#39;127.0.0.1&#39;</span>
</span></span><span class="line"><span class="cl">    <span class="n">PORT</span> <span class="o">=</span> <span class="mi">9500</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">data</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="n">socket</span><span class="o">.</span><span class="n">socket</span><span class="p">(</span><span class="n">socket</span><span class="o">.</span><span class="n">AF_INET</span><span class="p">,</span> <span class="n">socket</span><span class="o">.</span><span class="n">SOCK_STREAM</span><span class="p">)</span> <span class="k">as</span> <span class="n">s</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">s</span><span class="o">.</span><span class="n">connect</span><span class="p">((</span><span class="n">HOST</span><span class="p">,</span> <span class="n">PORT</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="n">s</span><span class="o">.</span><span class="n">sendall</span><span class="p">(</span><span class="sa">b</span><span class="s1">&#39;Hello World&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">data</span> <span class="o">=</span> <span class="n">s</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">1024</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="n">data</span><span class="o">.</span><span class="n">decode</span><span class="p">()</span> <span class="o">==</span> <span class="s2">&#34;Received&#34;</span>
</span></span></code></pre></div><p>The part of our code is the actual test. It sends &ldquo;Hello World&rdquo; to the TCP server, remember if you sent it as string (without <em>b&rsquo;&rsquo;</em>) then you&rsquo;ll have to encode that string first (<em>&ldquo;Hello World&rdquo;.encode()</em>). Then we capture the response from the server and decode it (because it&rsquo;s in binary, this converts it into a string) and assert or check that the data is the string &ldquo;Received&rdquo; as we expect..</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2018-11-05-pytest-with-background-thread-fixtures/source_code">Example source code</a></li>
<li><a href="https://docs.pytest.org/en/latest/">Pytest</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Running Expo/React Native in Docker</title>
      <link>https://haseebmajid.dev/posts/2018-10-31-running-expo-react-native-in-docker/</link>
      <pubDate>Wed, 31 Oct 2018 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2018-10-31-running-expo-react-native-in-docker/</guid>
      <description>&lt;p&gt;Running Expo/React Native in a Docker container can sometimes cause issues. In this example, I will be running
Docker 🐳 within a guest VM (Ubuntu) which will run on my host machine (Windows). My host machine will also
be running another VM as the Android emulator (Genymotion) for Expo to connect to. You can get a more
detailed post about how to connect two VMs together
&lt;a href=&#34;https://haseebmajid.dev/posts/react-native-with-virtualbox/&#34;&gt;here&lt;/a&gt;,
#Plug 🔌🔌🔌. Since I&amp;rsquo;ve set up networking on those two VMs already as far as Expo is concerned
it might as well be running on the host machine (Windows). Also in this example, I will be testing
this on an Android device.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Running Expo/React Native in a Docker container can sometimes cause issues. In this example, I will be running
Docker 🐳 within a guest VM (Ubuntu) which will run on my host machine (Windows). My host machine will also
be running another VM as the Android emulator (Genymotion) for Expo to connect to. You can get a more
detailed post about how to connect two VMs together
<a href="https://haseebmajid.dev/posts/react-native-with-virtualbox/">here</a>,
#Plug 🔌🔌🔌. Since I&rsquo;ve set up networking on those two VMs already as far as Expo is concerned
it might as well be running on the host machine (Windows). Also in this example, I will be testing
this on an Android device.</p>
<p><img
        loading="lazy"
        src="/posts/2018-10-31-running-expo-react-native-in-docker/images/docker-nyan.gif"
        type=""
        alt="Original Image: https://maraaverick.rbind.io/2017/11/docker-izing-your-work-in-r/ and https://tutuappapkdownload.com/expo-apk/"
        
      /></p>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li><a href="https://docs.docker.com/install/">Install Docker</a></li>
<li>(optional) <a href="https://docs.docker.com/compose/install/">Install docker-compose</a></li>
<li>Android device/emulator to test on</li>
</ul>
<h2 id="docker">Docker</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;main&#34;</span><span class="p">:</span> <span class="s2">&#34;node_modules/expo/AppEntry.js&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;private&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;scripts&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;android&#34;</span><span class="p">:</span> <span class="s2">&#34;expo-cli start --android&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;ios&#34;</span><span class="p">:</span> <span class="s2">&#34;expo-cli start --ios&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;start&#34;</span><span class="p">:</span> <span class="s2">&#34;expo-cli start&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;dependencies&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;expo&#34;</span><span class="p">:</span> <span class="s2">&#34;30.0.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;expo-cli&#34;</span><span class="p">:</span> <span class="s2">&#34;2.2.4&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;react&#34;</span><span class="p">:</span> <span class="s2">&#34;16.3.1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;react-native&#34;</span><span class="p">:</span> <span class="s2">&#34;https://github.com/expo/react-native/archive/sdk-30.0.0.tar.gz&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The <code>package.json</code> file I will be using the following example is a very barebones file, just including the minimum
packages required to run Expo.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-docker" data-lang="docker"><span class="line"><span class="cl"><span class="k">FROM</span><span class="s"> node:latest</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">LABEL</span> <span class="nv">version</span><span class="o">=</span><span class="m">1</span>.2.1<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">ENV</span> <span class="nv">ADB_IP</span><span class="o">=</span><span class="s2">&#34;192.168.1.1&#34;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">ENV</span> <span class="nv">REACT_NATIVE_PACKAGER_HOSTNAME</span><span class="o">=</span><span class="s2">&#34;192.255.255.255&#34;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">EXPOSE</span><span class="s"> 19000</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">EXPOSE</span><span class="s"> 19001</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">RUN</span> apt-get update <span class="o">&amp;&amp;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    apt-get install android-tools-adb<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">WORKDIR</span><span class="s"> /app</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">COPY</span> package.json yarn.lock app.json ./<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">RUN</span> yarn --network-timeout <span class="m">100000</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="k">CMD</span> adb connect <span class="nv">$ADB_IP</span> <span class="o">&amp;&amp;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>        yarn run android<span class="err">
</span></span></span></code></pre></div><p><code>FROM node:latest</code></p>
<p>Tells us which Docker Image we are using as a base, in this case, the official node.js image. This is because it
will have a lot of the dependencies we need already installed such as yarn and npm.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">ENV ADB_IP=&#34;192.168.1.1&#34;
</span></span><span class="line"><span class="cl">ENV REACT_NATIVE_PACKAGER_HOSTNAME=&#34;192.255.255.255&#34;
</span></span></code></pre></div><p>Sets an environment variable which can be accessed during runtime of the Docker container. Strictly speaking, these
don&rsquo;t need to be here because we can always inject into the Docker container at runtime, but I like to have the
environment variables documented.</p>
<p>The <strong>ADB_IP</strong> is IP of the Android device 📱 to connect to. The <strong>REACT_NATIVE_PACKAGER_HOSTNAME</strong> environment variable is
very important because it sets which IP address Expo (cli) is running on, this is the IP Address your phone will try to
connect to. If this is not set correctly, you&rsquo;ll get an error similar to Figure 1. You can work out the correct IP
address on Linux by using the following command. The first one should host IP (192.168.27.128 on my machine).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">hostname -I
</span></span><span class="line"><span class="cl">192.168.27.128 192.168.130.128 172.17.0.1 172.19.0.1 172.18.0.1
</span></span></code></pre></div><p>The reason this environment variable needs to be set is because by default the React Native packager
(which expo relies on) picks the first IP it sees on the machine, hence you can run expo on your host machine
fine but when you run in a Docker container you cannot connect to it because it&rsquo;s trying to use the Docker
IP address (one of the ones starting with 172.xxx.xxx.xxx).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">EXPOSE 19000
</span></span><span class="line"><span class="cl">EXPOSE 19001
</span></span></code></pre></div><p>This is essentially meta data letting the user of the Docker container know that they can access data on those ports.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">RUN apt-get update <span class="o">&amp;&amp;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    apt-get install android-tools-adb
</span></span></code></pre></div><p>Install Android Debugging Bridge (ADB), which is used to connect to an Android device and debug the application.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">COPY package.json yarn.lock app.json ./
</span></span><span class="line"><span class="cl">RUN yarn --network-timeout 100000
</span></span></code></pre></div><p>Copy some important files from host to Docker container. The <code>package.json</code> and <code>yarn.lock</code> are used to install
the dependencies and <code>app.json</code> is required by expo as a bare minimum.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">CMD adb connect $ADB_IP &amp;&amp; \
</span></span><span class="line"><span class="cl">    yarn run android
</span></span><span class="line"><span class="cl">    # runs expo-cli start --android
</span></span></code></pre></div><p><img
        loading="lazy"
        src="/posts/2018-10-31-running-expo-react-native-in-docker/images/error-emulator.png"
        type=""
        alt="Figure 1: Could not connect error 😢"
        
      /></p>
<h2 id="running-docker">Running Docker</h2>
<p>This command runs when the Docker Image is first to run, every other command is used to build to the image itself. This
uses an environment variable passed into the Docker container and connects to the Android device at $ADB<em>IP. Then run
the <strong>android</strong> command in _package.json</em>. Then you can simply run the following commands to build and start your Docker container.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker build -t expo-android .
</span></span><span class="line"><span class="cl">docker run -e <span class="nv">ADB_IP</span><span class="o">=</span>192.168.112.101 <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>            -e <span class="nv">REACT_NATIVE_PACKAGER_HOSTNAME</span><span class="o">=</span>192.168.1.1 <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>            -p 19000:19000 <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>            -p 19001:19001 <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>            expo-android
</span></span></code></pre></div><ul>
<li>-t is used to name the image (expo-android)</li>
<li>. tells Docker where the Dockerfile is (in the current directory)</li>
<li>&ndash;env sets environment used by Docker container when it starts to run (REACT_NATIVE_PACKAGER_HOSTNAME andADB_IP are overwritten using these new values)</li>
<li>-p publishes ports, in this example, it maps port 19000 on the host to port 19000 on the Docker container (and also 19001), as we need to access port 19000 and 19001 so that Expo (expo-cli) can connect to our Android device.</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;3.5&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">expo_android</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">expo_android</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">build</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">context</span><span class="p">:</span><span class="w"> </span><span class="l">.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">dockerfile</span><span class="p">:</span><span class="w"> </span><span class="l">Dockerfile</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">env_file</span><span class="p">:</span><span class="w"> </span><span class="l">.env</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">19000</span><span class="p">:</span><span class="m">19000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">19001</span><span class="p">:</span><span class="m">19001</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">${PWD}/:/app/</span><span class="w">
</span></span></span></code></pre></div><p>Since expo is being used to build mobile phone applications Docker isn&rsquo;t going to be used in production. I prefer to use
docker-compose to do the building and running, it means I can run one simple command and do the building and running in
one step. Quick aside docker-compose is great for development, especially when you need to run multiple Docker container,
but is not really built to be used in production. Look at using a container orchestration tool such as Kubernetes.</p>
<p>I also mount my current directory on the host machine to /app/ directory on the docker container, this is so that any
files that change on my host machine will also change in the Docker container, rather than having to build the
Docker container again.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker-compose up --build -d
</span></span></code></pre></div><h3 id="environment-variables">Environment Variables</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-env" data-lang="env"><span class="line"><span class="cl"><span class="nv">ADB_IP</span><span class="o">=</span><span class="s2">&#34;192.168.112.101&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">REACT_NATIVE_PACKAGER_HOSTNAME</span><span class="o">=</span><span class="s2">&#34;192.168.27.128&#34;</span>
</span></span></code></pre></div><p>An example <code>.env</code> file used to pass environment variables (using docker-compose) to the Docker container.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2018-10-31-running-expo-react-native-in-docker/source_code">Example source code</a></li>
<li><a href="https://medium.freecodecamp.org/a-beginner-friendly-introduction-to-containers-vms-and-docker-79a9e3e119b">Docker explained</a></li>
<li><a href="https://github.com/react-community/create-react-native-app/issues/81">GitHub issue around could not connect errors</a></li>
<li><a href="https://www.genymotion.com/">Genymotion emulator</a></li>
<li><a href="https://ezgif.com/overlay">GIF overlay creator (Nyan Docker)</a></li>
<li>React logo from <a href="https://seeklogo.com/vector-logo/273845/react">here</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Debugging A React Native WebView</title>
      <link>https://haseebmajid.dev/posts/2018-10-27-debugging-a-react-native-webview/</link>
      <pubDate>Sat, 27 Oct 2018 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2018-10-27-debugging-a-react-native-webview/</guid>
      <description>&lt;p&gt;The &lt;em&gt;core&lt;/em&gt; logic of my React Native app involves using WebViews because I need to access the HTML5 canvas. Whilst
developing this code there are bound to be errors and issues with the WebView code. Figuring out how to debug
code within the WebView isn&amp;rsquo;t so obvious.&lt;/p&gt;
&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2018-10-27-debugging-a-react-native-webview/images/webview-dev-tools.png&#34;
        type=&#34;&#34;
        alt=&#34;Figure 1: Chrome Inspect for the WebView&#34;
        
      /&gt;&lt;/p&gt;
&lt;h2 id=&#34;option-1-chrome-inspect&#34;&gt;Option 1: Chrome Inspect&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Start your Expo/React Native app*.&lt;/li&gt;
&lt;li&gt;Open and chrome and then go to the following URL, &lt;a href=&#34;chrome://inspect&#34;&gt;chrome://inspect&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Then click on the &lt;em&gt;inspect&lt;/em&gt; button, click the top link to open the latest WebView, you should see something similar to Figure 2.&lt;/li&gt;
&lt;li&gt;You should see something similar to Figure 1. Now you explore the WebView like a normal web page.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;https://haseebmajid.dev/posts/2018-10-27-debugging-a-react-native-webview/images/chrome-inspect.png&#34;
        type=&#34;&#34;
        alt=&#34;Figure 2: List of WebView&#34;
        
      /&gt;&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>The <em>core</em> logic of my React Native app involves using WebViews because I need to access the HTML5 canvas. Whilst
developing this code there are bound to be errors and issues with the WebView code. Figuring out how to debug
code within the WebView isn&rsquo;t so obvious.</p>
<p><img
        loading="lazy"
        src="/posts/2018-10-27-debugging-a-react-native-webview/images/webview-dev-tools.png"
        type=""
        alt="Figure 1: Chrome Inspect for the WebView"
        
      /></p>
<h2 id="option-1-chrome-inspect">Option 1: Chrome Inspect</h2>
<ul>
<li>Start your Expo/React Native app*.</li>
<li>Open and chrome and then go to the following URL, <a href="chrome://inspect">chrome://inspect</a>.</li>
<li>Then click on the <em>inspect</em> button, click the top link to open the latest WebView, you should see something similar to Figure 2.</li>
<li>You should see something similar to Figure 1. Now you explore the WebView like a normal web page.</li>
</ul>
<p><img
        loading="lazy"
        src="/posts/2018-10-27-debugging-a-react-native-webview/images/chrome-inspect.png"
        type=""
        alt="Figure 2: List of WebView"
        
      /></p>
<h2 id="option-2-react-native-debugger">Option 2: React Native Debugger</h2>
<p>Alternatively you can access this page using the React Native Debugger page in chrome.</p>
<ul>
<li>Start your Expo/React Native app*</li>
<li>Go to your React Native debugger on chrome, in my case since I&rsquo;m using Expo my URL is http://192.168.27.128:19001/debugger-ui/</li>
<li>Go to the three dots button (next to close button) &gt; More tools &gt; Remote devices</li>
<li>At the bottom you&rsquo;ll see another panel open up</li>
<li>You should be able to see your Android device in the list</li>
<li>Hit the Inspect button the first item (this will be the latest)</li>
<li>Now you explore the WebView like a normal web page</li>
</ul>
<h2 id="debugging">Debugging</h2>
<p>So to the closest way to debug the WebView I have found is to use console.log statements that will appear in the
DevTools console. In the normal React Native debugger console you won&rsquo;t be able to see any console.log statements
from the WebView. You can only view them by exploring DevTools for Remote devices. As far as I can tell you cannot
use breakpoints in WebViews but still this is better than nothing.</p>
<p><img
        loading="lazy"
        src="/posts/2018-10-27-debugging-a-react-native-webview/images/devtools.gif"
        type=""
        alt="Figure 3: Open DevTools for Remote devices"
        
      /></p>
<h2 id="appendix">Appendix</h2>
<p>Make sure to do this on the same machine/host that is running the emulator. For example, a Genymotion VM is
running on my Windows machine but I can still see it&rsquo;s WebViews on the chrome inspect URL. This can be seen in Figure 3.</p>
<p><img
        loading="lazy"
        src="/posts/2018-10-27-debugging-a-react-native-webview/images/chrome-inspect-emulator.png"
        type=""
        alt="Figure 4: List of WebView"
        
      /></p>
<h3 id="links">Links</h3>
<ul>
<li><a href="https://stackoverflow.com/questions/47711418/debugging-webview-in-react-native-apps?rq=1">With help from this StackOverflow post</a></li>
<li><a href="https://www.genymotion.com/">Genymotion</a></li>
<li>GIFs created with <a href="https://www.screentogif.com/">screentogif</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Inheritance in SQLAlchemy (with Flask)</title>
      <link>https://haseebmajid.dev/posts/2018-10-18-inheritance-in-sqlalchemy-with-flask/</link>
      <pubDate>Thu, 18 Oct 2018 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2018-10-18-inheritance-in-sqlalchemy-with-flask/</guid>
      <description>&lt;p&gt;SQLAlchemy is an Object-relational mapping (ORM) made for the Python programming language. ORMs in theory allow
programmers to abstract away SQL. In simple terms they allow us to interact with a database using purely Python
(objects/functions). I will be using the flask-SQLAlchemy extension for my examples.&lt;/p&gt;
&lt;p&gt;Each table is referred to as a model, each model is simply just a python class and each attribute of that class
becomes a column in an SQL table. The database is made up of multiple models. Just like with normal Python models
can inherit from other models and share attributes with the parent model. This is very useful if you going to
have models that will store similar types of data.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>SQLAlchemy is an Object-relational mapping (ORM) made for the Python programming language. ORMs in theory allow
programmers to abstract away SQL. In simple terms they allow us to interact with a database using purely Python
(objects/functions). I will be using the flask-SQLAlchemy extension for my examples.</p>
<p>Each table is referred to as a model, each model is simply just a python class and each attribute of that class
becomes a column in an SQL table. The database is made up of multiple models. Just like with normal Python models
can inherit from other models and share attributes with the parent model. This is very useful if you going to
have models that will store similar types of data.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">flask_sqlalchemy</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">db</span> <span class="o">=</span> <span class="n">flask_sqlalchemy</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Pets</span><span class="p">(</span><span class="n">db</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">__abstract__</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="cl">    <span class="n">name</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">Column</span><span class="p">(</span><span class="n">db</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="mi">100</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="n">price</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">Column</span><span class="p">(</span><span class="n">db</span><span class="o">.</span><span class="n">Integer</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">breed</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">Column</span><span class="p">(</span><span class="n">db</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="mi">100</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Cats</span><span class="p">(</span><span class="n">Pets</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">__tablename__</span> <span class="o">=</span> <span class="s1">&#39;cats&#39;</span>
</span></span><span class="line"><span class="cl">    <span class="nb">id</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">Column</span><span class="p">(</span><span class="n">db</span><span class="o">.</span><span class="n">Integer</span><span class="p">,</span> <span class="n">primary_key</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Dogs</span><span class="p">(</span><span class="n">Pets</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">__tablename__</span> <span class="o">=</span> <span class="s1">&#39;dogs&#39;</span>
</span></span><span class="line"><span class="cl">    <span class="nb">id</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">Column</span><span class="p">(</span><span class="n">db</span><span class="o">.</span><span class="n">Integer</span><span class="p">,</span> <span class="n">primary_key</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span></code></pre></div><p>Taking a look at the <em>models.py</em> module, we define an abstract class called Pets. Which means SQLAlchemy will not create
a table for that model. Our next two models Cats and Dogs inherit all the attributes form Pets. So Cats and Dog tables
will each have a column called name, price and breed. The main advantage of this is if you ever need to change the
models you just have to change it in once place. The more models that inherit from the base model.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">flask</span> <span class="kn">import</span> <span class="n">Flask</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">.models</span> <span class="kn">import</span> <span class="n">db</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">uri</span> <span class="o">=</span> <span class="s2">&#34;mysql+pymysql://user:password@localhost:3306/test&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">app</span><span class="o">.</span><span class="n">config</span><span class="p">[</span><span class="s1">&#39;SQLALCHEMY_DATABASE_URI&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">uri</span>
</span></span><span class="line"><span class="cl"><span class="n">app</span><span class="o">.</span><span class="n">config</span><span class="p">[</span><span class="s1">&#39;SQLALCHEMY_TRACK_MODIFICATIONS&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="cl"><span class="n">app</span><span class="o">.</span><span class="n">app_context</span><span class="p">()</span><span class="o">.</span><span class="n">push</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">db</span><span class="o">.</span><span class="n">init_app</span><span class="p">(</span><span class="n">app</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">db</span><span class="o">.</span><span class="n">create_all</span><span class="p">()</span>
</span></span></code></pre></div><p>Above is an example <code>__init__.py</code> file to initialise the database and create all the database tables from the
models. That&rsquo;s it folks, thanks for reading.</p>
<p><strong>Please</strong> note there are other ways to implement inheritance with SQLAlchemy, I personally found this way to be the
cleanest in terms of code readability.</p>
<hr>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2018-10-18-inheritance-in-sqlalchemy-with-flask/source_code/source_code/example">Example source code</a></li>
<li><a href="https://www.sqlalchemy.org/">SQLAlchemy</a></li>
<li><a href="http://flask-sqlalchemy.pocoo.org/2.3/">flask-sqlalchemy</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Debugging React Native apps in WebStorm and Visual Studio Code</title>
      <link>https://haseebmajid.dev/posts/2018-09-07-debugging-react-native-apps-in-webstorm-and-visual-studio-code/</link>
      <pubDate>Fri, 07 Sep 2018 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2018-09-07-debugging-react-native-apps-in-webstorm-and-visual-studio-code/</guid>
      <description>&lt;p&gt;Visual Studio Code and WebStorm are two popular editors for developing React Native/Expo apps. These editors have lots
of useful features, such as syntax highlighting, git integration and auto completion. However working out to debug
Expo apps can be a bit confusing.&lt;/p&gt;
&lt;p&gt;One of the main advantages of working in an editor/IDE (let us be honest Visual Studio Code is pretty close to an IDE)
is being able to use a debugger (and breakpoints) to go through your code line by line and see which part of your code
is not running as expected. Debuggers can be a powerful tool when trying to remove bugs from your software, being able
to stop your code at any point and check the state, analyse variables etc.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Visual Studio Code and WebStorm are two popular editors for developing React Native/Expo apps. These editors have lots
of useful features, such as syntax highlighting, git integration and auto completion. However working out to debug
Expo apps can be a bit confusing.</p>
<p>One of the main advantages of working in an editor/IDE (let us be honest Visual Studio Code is pretty close to an IDE)
is being able to use a debugger (and breakpoints) to go through your code line by line and see which part of your code
is not running as expected. Debuggers can be a powerful tool when trying to remove bugs from your software, being able
to stop your code at any point and check the state, analyse variables etc.</p>
<p>You can actually debug, with breakpoints and all those other fancy features, your Expo apps in both IDEs
(WebStorm and Visual Studio Code). I will be running my app in a <a href="https://genymotion.com/">Genymotion Android emulator</a>.
Also please note there are likely several ways to debug your React Native/Expo app , I’m simply showing you the way
I was able to get my debugger running.</p>
<p>
  <img
    loading="lazy"
    src="https://media.giphy.com/media/ThOHTmBZlHOQ8/giphy.gif"
    alt="Debugging"
    
  /></p>
<h2 id="prerequisite">Prerequisite</h2>
<ul>
<li>Have an <a href="https://expo.io/learn">Expo</a> app</li>
<li>Turn on &ldquo;Debug JS Remotely&rdquo; on your android device, as shown in Figure 1. In Genymotion you can open the &ldquo;Developer Menu&rdquo; menu by pressing CTRL + M. More information <a href="http://facebook.github.io/react-native/docs/debugging">here</a></li>
<li>Connect to your Android device using ADB</li>
</ul>
<p><code>adb connect 192.168.101.1 # Replace with your Device IP</code></p>
<p><img
        loading="lazy"
        src="/posts/2018-09-07-debugging-react-native-apps-in-webstorm-and-visual-studio-code/images/android-emulator.png"
        type=""
        alt="Figure 1: Turn on &ldquo;Debug JS Remotely&rdquo; on an Android device"
        
      /></p>
<h2 id="webstrom">WebStrom</h2>
<ul>
<li>Install and turn on CORS plugin in Chrome, make sure it&rsquo;s the same chrome browser you use in your configuration. (NOTE make sure your aware of the security implication on using CORS, switch the plugin off when your not using it).</li>
<li>Open WebStorm and your Expo project</li>
<li>Add the following lines of code to your <em>package.json</em> file</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="s2">&#34;scripts&#34;</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;start&#34;</span><span class="p">:</span> <span class="s2">&#34;exp start&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><ul>
<li>Create a new configuration of type &ldquo;React Native&rdquo; with the settings shown in Figure 3. Remember to add a new the &ldquo;Before launch&rdquo; configuration and select configuration type &ldquo;React Native Bundler&rdquo;</li>
<li>Run the configuration you just created in debug mode (SHIFT + F9)</li>
<li>Open a terminal window and run</li>
</ul>
<p><code>exp android</code></p>
<p><img
        loading="lazy"
        src="/posts/2018-09-07-debugging-react-native-apps-in-webstorm-and-visual-studio-code/images/webstorm-configuration.png"
        type=""
        alt="Figure 2: Run/Debug Configuration settings."
        
      /></p>
<p><img
        loading="lazy"
        src="/posts/2018-09-07-debugging-react-native-apps-in-webstorm-and-visual-studio-code/images/webstorm.gif"
        type=""
        alt="Figure 3: WebStorm; Starting the Debugger"
        
      /></p>
<h2 id="visual-studio-code">Visual Studio Code</h2>
<ul>
<li>Open Visual Studio Code and your Expo project</li>
<li>Create a new file in your vscode folder called <code>settings.json</code> and add the following</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;react-native&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;packager&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;port&#34;</span><span class="p">:</span> <span class="mi">19001</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><ul>
<li>Go to &ldquo;Debug&rdquo; window (CTRL + SHIFT + D)</li>
<li>When prompted for environment choose &ldquo;React Native&rdquo;, it should give an <code>launch.json</code> file like this, make sure it contains the following:</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="err">[</span>
</span></span><span class="line"><span class="cl">        <span class="err">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;Attach to packager&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;program&#34;</span><span class="p">:</span> <span class="s2">&#34;${workspaceRoot}/.vscode/launchReactNative.js&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;reactnative&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;request&#34;</span><span class="p">:</span> <span class="s2">&#34;attach&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;sourceMaps&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;outDir&#34;</span><span class="p">:</span> <span class="s2">&#34;${workspaceRoot}/.vscode/.react&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span><span class="err">,</span>
</span></span><span class="line"><span class="cl">    <span class="err">]</span>
</span></span><span class="line"><span class="cl"><span class="err">}</span>
</span></span></code></pre></div><ul>
<li>Open the integrated terminal (CTRL + `) and run</li>
</ul>
<p><code>exp start</code></p>
<ul>
<li>On top of the Debug menu should say &ldquo;Debug Android&rdquo; change this to &ldquo;Attach to packager&rdquo; and press &ldquo;Start Debugging&rdquo;</li>
<li>Open another terminal and run</li>
</ul>
<p><code>exp android</code></p>
<ul>
<li>Swap to &ldquo;Debug Console&rdquo; tab to see the debugger</li>
</ul>
<p><img
        loading="lazy"
        src="/posts/2018-09-07-debugging-react-native-apps-in-webstorm-and-visual-studio-code/images/visual-studio-code.gif"
        type=""
        alt="Figure 4: Visual Studio Code; Starting the Debugger."
        
      /></p>
<h2 id="appendix">Appendix</h2>
<ul>
<li>GIFs created with <a href="https://www.screentogif.com/">screentogif</a></li>
<li><a href="https://medium.com/r/?url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3D0_MnXPD55-E">&ldquo;Debugging create-react-native-app with VSCode&rdquo;</a></li>
<li><a href="https://blog.jetbrains.com/webstorm/2018/02/webstorm-2018-1-eap-181-3263/">&ldquo;WebStorm 2018.1 EAP, 181.3263.21: improvements for React Native, extract Vue component&rdquo;</a></li>
<li>Icon made by <a href="https://www.flaticon.com/authors/monkik">Monik</a> from <a href="https://www.flaticon.com/">FlatIcon</a>and is licensed by <a href="http://creativecommons.org/licenses/by/3.0/">Creative Commons 3.0</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>React Native/Expo with VirtualBox and Genymotion</title>
      <link>https://haseebmajid.dev/posts/2018-09-03-react-native-expo-with-virtualbox-and-genymotion/</link>
      <pubDate>Mon, 03 Sep 2018 00:00:00 +0000</pubDate>
      
      <guid>https://haseebmajid.dev/posts/2018-09-03-react-native-expo-with-virtualbox-and-genymotion/</guid>
      <description>&lt;p&gt;My home PC runs Windows for various conveniences, such as gaming. However, for development, I run an Ubuntu virtual
machine (VM) and Genymotion (on Windows) for testing my app. Genymotion also uses VirtualBox to run its Android
emulators. So we work out how to let two VMs running on the same host communicate with each other
(Ubuntu and Android Emulator).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Please Note:&lt;/strong&gt; This will also work for VMWare Player.&lt;/p&gt;
&lt;h2 id=&#34;solution&#34;&gt;Solution&lt;/h2&gt;
&lt;p&gt;There are a few networking options we can choose from when setting up a VM.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>My home PC runs Windows for various conveniences, such as gaming. However, for development, I run an Ubuntu virtual
machine (VM) and Genymotion (on Windows) for testing my app. Genymotion also uses VirtualBox to run its Android
emulators. So we work out how to let two VMs running on the same host communicate with each other
(Ubuntu and Android Emulator).</p>
<p><strong>Please Note:</strong> This will also work for VMWare Player.</p>
<h2 id="solution">Solution</h2>
<p>There are a few networking options we can choose from when setting up a VM.</p>
<ul>
<li>
<p><strong>NAT</strong>: Allows your VMs so communicate with the outside world (outbound network), but your host machine (Windows)has no way to access this network</p>
</li>
<li>
<p><strong>Bridged</strong>: Allows your VMs to access a network using the host computer&rsquo;s Ethernet adapter. However this adds a bit of complexity, as your VM needs it own identity on the network as essentially its treated like another physical machine.</p>
</li>
<li>
<p><strong>Host-only</strong>: Acts a hybrid between the other two options. It allows your VM to communicate with your host machine. So essentially all we need for multiple VMs to communicate with each is other is to turn on Host-only network (on all VMs). This allow them to communicate with the host machine which will act as a &ldquo;bridge&rdquo; (no pun intended).</p>
</li>
</ul>
<p><img
        loading="lazy"
        src="/posts/2018-09-03-react-native-expo-with-virtualbox-and-genymotion/images/network.png"
        type=""
        alt="Network Diagaram"
        
      /></p>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li><a href="https://www.virtualbox.org/wiki/Downloads">VirtualBox</a> Installed on the host machine</li>
<li>An <a href="https://docs.expo.io/versions/latest/workflow/create-react-native-app">Expo</a> app in the (Ubuntu) development VM</li>
<li><a href="https://www.genymotion.com/desktop/">Genymotion</a> Installed and setup on the host machine</li>
</ul>
<h2 id="virtualbox">VirtualBox</h2>
<ul>
<li>Open up VirtualBox</li>
<li>Click on the development VM and open the &ldquo;Settings&rdquo;.</li>
<li>Go to &ldquo;Network&rdquo; and Set the following two adapters, as shown in Figure 1</li>
<li>Adapter 1: &ldquo;Host-only Adapter&rdquo;</li>
<li>Adapter 2: &ldquo;NAT&rdquo;</li>
</ul>
<p>NAT allows the Ubuntu VM to use the host machines internet access, it allows it to communicate with the outside world.
The Host-only Adapter allows the Ubuntu VM to communicate with other VM, such as the Genymotion Android emulator.</p>
<p><img
        loading="lazy"
        src="/posts/2018-09-03-react-native-expo-with-virtualbox-and-genymotion/images/ubuntu_network1.png"
        type=""
        alt="Figure 1: Ubuntu VM network settings"
        
      />
<img
        loading="lazy"
        src="/posts/2018-09-03-react-native-expo-with-virtualbox-and-genymotion/images/ubuntu_network2.png"
        type=""
        alt="Figure 1: Ubuntu VM network settings"
        
      /></p>
<h2 id="genymotion">Genymotion</h2>
<p>Genymotion network settings are pretty much the same as the Ubuntu VM, as shown in Figure 2. Please note that the
Host-only adapters can be the same (i.e. #7).</p>
<p><img
        loading="lazy"
        src="/posts/2018-09-03-react-native-expo-with-virtualbox-and-genymotion/images/phone_network1.png"
        type=""
        alt="Figure 2: Android emulator VM network settings"
        
      />
<img
        loading="lazy"
        src="/posts/2018-09-03-react-native-expo-with-virtualbox-and-genymotion/images/phone_network2.png"
        type=""
        alt="Figure 2: Android emulator VM network settings"
        
      /></p>
<h2 id="expo">Expo</h2>
<ul>
<li>Start your Genymotion Android emulator.</li>
<li>Start your Ubuntu VM and open a terminal.</li>
<li>Replace the IP Address with your own one, you can find your IP address on the Android emulator. As shown in Figure 3.</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">adb connect 192.168.112.101
</span></span><span class="line"><span class="cl">exp start
</span></span><span class="line"><span class="cl">exp android
</span></span></code></pre></div><p><img
        loading="lazy"
        src="/posts/2018-09-03-react-native-expo-with-virtualbox-and-genymotion/images/genymotion_ip.png"
        type=""
        alt="Figure 3: Genymotion Android emulator&rsquo;s IP address"
        
      /></p>
<p>That&rsquo;s it you should see your app running on the Genymotion Android emulator now. You can see how to start the
application below in Figure 4.</p>
<p><img
        loading="lazy"
        src="/posts/2018-09-03-react-native-expo-with-virtualbox-and-genymotion/images/connecting.gif"
        type=""
        alt="Figure 4: Starting Expo application on Android emulator"
        
      /></p>
<h2 id="appendix">Appendix</h2>
<ul>
<li>GIFs created with <a href="https://www.screentogif.com/">screentogif</a></li>
<li><a href="http://bertvv.github.io/notes-to-self/2015/09/29/virtualbox-networking-an-overview/">Detailed post about VirtualBox Networking</a></li>
<li>Drawing made with <a href="https://www.draw.io/">draw.io</a></li>
</ul>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
