<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
<title>unitehenry</title>
<link>https://unitehenry.com/blog</link>
<description>cracked software engineer</description>
<language>en-us</language>
<item>
<title>Hyprland on dGPU Laptop</title>
<link>https://unitehenry.com/blog/hyprland-for-dgpu-laptop</link>
<description>This guide details a clever Arch Linux + Hyprland setup on a
dual-boot NVIDIA dGPU laptop, allowing the user to boot directly into
Hyprland on an external monitor while keeping the laptop lid closed — no
keyboard interaction required.</description>
<pubDate>Fri, 14 Nov 2025 00:00:00 +0000</pubDate>
<content:encoded><![CDATA[
      <!-- prettier-ignore -->
<h1 id="hyprland-on-dgpu-laptop">Hyprland on dGPU Laptop</h1>
<p>2025-11-14</p>
<figure>
<img alt="“Amazon Laptop”" src="/images/4743cffc-b34b-415c-8333-1e6f47be1efc.png">
<figcaption aria-hidden="true">“Amazon Laptop”</figcaption>
</figure>
<p>Last year I bought this laptop to dual-boot Archlinux and Windows. I
bought a dedicated NVMe drive, installed Arch, and setup a GRUB boot
partition to switch between OS.</p>
<h2 id="my-setup">My Setup</h2>
<p>I strictly like to use a single monitor and keep my laptop lid shut.
So after installing Arch and <a href="https://hypr.land/">Hyprland</a>,
booting my laptop with lid shut and directly into Hyprland on my
external monitor was problematic. I would have to first open my laptop
lid, login to archlinux, and set Hyprland to use my dGPU over the
iGPU.</p>
<h2 id="the-problem">The Problem</h2>
<p>After installing Arch and Hyprland, there’s a couple problems that
come with achieving my ideal setup:</p>
<ul>
<li><a href="#booting-directly-into-desktop-envrionment">Booting
directly into desktop environment</a></li>
<li><a href="#switching-displays">Switching between external monitor or
laptop display</a></li>
<li><a href="#dual-boot">Choosing which OS to boot into</a></li>
</ul>
<h2 id="booting-directly-into-desktop-environment">Booting Directly Into
Desktop Environment</h2>
<p>By default, Arch uses <a href="https://man.archlinux.org/man/agetty.8.en"><code>agetty</code></a>
to open a tty port, prompting login, and starting that user’s shell. It
comes with a <code>systemd</code> unit that looks like this:</p>
<pre><code>[Service]
ExecStart=-/sbin/agetty --noreset --noclear --issue-file=/etc/issue:/etc/issue.d:/run/issue.d:/usr/lib/issue.d - ${TERM}
Type=idle
Restart=always</code></pre>
<p>One thing nice about <code>agetty</code> is that is comes with a
<code>--autologin</code>, so when my system starts it will open a shell
for my user without having to login. With a simple bash script in my
user’s <code>.bashrc</code> file, I can invoke Hyprland at startup.</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode sh"><code class="sourceCode bash"><span id="cb2-1"><a aria-hidden="true" href="#cb2-1" tabindex="-1"></a><span class="cf">if</span> <span class="bu">command</span> <span class="at">-v</span> hyprland <span class="op">&gt;</span> /dev/null <span class="dv">2</span><span class="op">&gt;&amp;</span><span class="dv">1</span><span class="kw">;</span> <span class="cf">then</span></span>
<span id="cb2-2"><a aria-hidden="true" href="#cb2-2" tabindex="-1"></a>   <span class="cf">if</span> <span class="kw">[[</span> <span class="ot">-z</span> <span class="st">"</span><span class="va">$HYPRLAND_INSTANCE_SIGNATURE</span><span class="st">"</span> <span class="kw">]];</span> <span class="cf">then</span></span>
<span id="cb2-3"><a aria-hidden="true" href="#cb2-3" tabindex="-1"></a>    <span class="ex">hyprland</span></span>
<span id="cb2-4"><a aria-hidden="true" href="#cb2-4" tabindex="-1"></a>   <span class="cf">fi</span></span>
<span id="cb2-5"><a aria-hidden="true" href="#cb2-5" tabindex="-1"></a><span class="cf">fi</span></span></code></pre></div>
<p>First checking if the <code>hyprland</code> command exists, then
checking the <code>HYPRLAND_INSTANCE_SIGNATURE</code> environment
variable to know if the bash session is currently running in a Hyprland
instance. If it’s not, fire up Hyprland!</p>
<h2 id="switching-displays">Switching Displays</h2>
<p>So now that my machine can fire up Hyprland from boot without ever
having to touch the keyboard, there needs to be a way to know whether
Hyprland should use the external monitor or laptop display. The default
behavior for this is sending display primarily to the integrated
display, and to the external monitor if connected. For my setup, I want
it to only use the external monitor if connected.</p>
<h3 id="detecting-monitor">Detecting Monitor</h3>
<p>My laptop has a discrete NVIDIA GPU that has an HDMI port which will
be primarily used for connected external monitors. After installing the
appropriate <a href="https://wiki.archlinux.org/title/NVIDIA">NVIDIA
drivers for Arch</a>, I can detect if there is a display attached. The
proprietary NVIDIA drivers come with an <a href="https://docs.nvidia.com/deploy/nvidia-smi/index.html"><code>nvidia-smi</code></a>
utility which can be used to detect if a display is connected by doing
the following:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode sh"><code class="sourceCode bash"><span id="cb3-1"><a aria-hidden="true" href="#cb3-1" tabindex="-1"></a><span class="ex">$</span> nvidia-smi <span class="at">--query-gpu</span> display_attached</span>
<span id="cb3-2"><a aria-hidden="true" href="#cb3-2" tabindex="-1"></a><span class="ex">display_attached</span></span>
<span id="cb3-3"><a aria-hidden="true" href="#cb3-3" tabindex="-1"></a><span class="ex">Yes</span></span></code></pre></div>
<p>By modifying the <code>.bashrc</code> script, I can pipe this
standard out into bash and use that to detect if Hyprland should
prioritize this GPU display:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode sh"><code class="sourceCode bash"><span id="cb4-1"><a aria-hidden="true" href="#cb4-1" tabindex="-1"></a><span class="cf">if</span> <span class="bu">command</span> <span class="at">-v</span> hyprland <span class="op">&gt;</span> /dev/null <span class="dv">2</span><span class="op">&gt;&amp;</span><span class="dv">1</span><span class="kw">;</span> <span class="cf">then</span></span>
<span id="cb4-2"><a aria-hidden="true" href="#cb4-2" tabindex="-1"></a>   <span class="cf">if</span> <span class="kw">[[</span> <span class="ot">-z</span> <span class="st">"</span><span class="va">$HYPRLAND_INSTANCE_SIGNATURE</span><span class="st">"</span> <span class="kw">]];</span> <span class="cf">then</span></span>
<span id="cb4-3"><a aria-hidden="true" href="#cb4-3" tabindex="-1"></a>     <span class="va">DISPLAY_ATTACHED_OUT</span><span class="op">=</span><span class="va">$(</span><span class="ex">nvidia-smi</span> <span class="at">--query-gpu</span> display_attached<span class="va">)</span></span>
<span id="cb4-4"><a aria-hidden="true" href="#cb4-4" tabindex="-1"></a> </span>
<span id="cb4-5"><a aria-hidden="true" href="#cb4-5" tabindex="-1"></a>     <span class="cf">if</span> <span class="kw">[[</span> <span class="va">$DISPLAY_ATTACHED_OUT</span> <span class="ot">==</span> <span class="pp">*</span><span class="st">"Yes"</span><span class="pp">*</span> <span class="kw">]];</span> <span class="cf">then</span></span>
<span id="cb4-6"><a aria-hidden="true" href="#cb4-6" tabindex="-1"></a>       <span class="bu">echo</span> <span class="st">"external display connected"</span><span class="kw">;</span></span>
<span id="cb4-7"><a aria-hidden="true" href="#cb4-7" tabindex="-1"></a>       <span class="va">AQ_DRM_DEVICES</span><span class="op">=</span><span class="st">"/dev/dri/card1:/dev/dri/card0"</span> <span class="ex">hyprland</span><span class="kw">;</span></span>
<span id="cb4-8"><a aria-hidden="true" href="#cb4-8" tabindex="-1"></a>     <span class="cf">else</span></span>
<span id="cb4-9"><a aria-hidden="true" href="#cb4-9" tabindex="-1"></a>       <span class="bu">echo</span> <span class="st">"external display disconnected"</span><span class="kw">;</span></span>
<span id="cb4-10"><a aria-hidden="true" href="#cb4-10" tabindex="-1"></a>       <span class="ex">hyprland</span></span>
<span id="cb4-11"><a aria-hidden="true" href="#cb4-11" tabindex="-1"></a>     <span class="cf">fi</span></span>
<span id="cb4-12"><a aria-hidden="true" href="#cb4-12" tabindex="-1"></a>   <span class="cf">fi</span></span>
<span id="cb4-13"><a aria-hidden="true" href="#cb4-13" tabindex="-1"></a><span class="cf">fi</span></span></code></pre></div>
<p>By setting the <a href="https://wiki.hypr.land/Configuring/Multi-GPU/"><code>AQ_DRM_DEVICES</code></a>
environment variable prior to running <code>hyprland</code>, we can tell
the compositor to prioritize the dGPU over the integrated Intel GPU.
This will result in Hyprland only using the external monitor.</p>
<h3 id="integrated-gpu">Integrated GPU</h3>
<p>Using the dGPU acceleration on the laptop display is something
Hyprland does not support out of the box, and setting the
<code>AQ_DRM_DEVICES</code> does not accomplish this either. This is
because the laptop display is connected directly to the iGPU which the
BIOS prioritizes over the dGPU by default. There is a UEFI setting that
allows you to configure priority over dGPU, but this can only be
accomplished if the hardware includes a MUX.</p>
<p>For my setup, I’m not concerned with GPU acceleration over the laptop
display since I use <a href="#dual-boot">dual booting</a> and do gaming
on Windows.</p>
<h2 id="dual-boot">Dual Boot</h2>
<figure>
<img alt="“GRUB Menu”" src="/images/3aeaf408-bf5a-4529-945f-efa8fd1e7e1a.jpg">
<figcaption aria-hidden="true">“GRUB Menu”</figcaption>
</figure>
<blockquote>
<p>But if the laptop lid is closed, how do you choose to boot into
Windows from the GRUB menu?</p>
</blockquote>
<div class="sourceCode" id="cb5"><pre class="sourceCode sh"><code class="sourceCode bash"><span id="cb5-1"><a aria-hidden="true" href="#cb5-1" tabindex="-1"></a><span class="fu">sudo</span> grub-reboot 2 <span class="kw">&amp;&amp;</span> <span class="ex">reboot</span><span class="kw">;</span></span></code></pre></div>
<p>By using this bash script, I can do the following to boot into
Windows:</p>
<ol type="1">
<li>Power on the device</li>
<li>Boot into Arch</li>
<li>Arch seamlessly load into Hyprland</li>
<li>NVIDIA SMI detects the monitor</li>
<li>Prioritize the dGPU for Hyprland</li>
<li>Hyprland sends display to the external monitor</li>
<li>Run <code>grub-reboot</code> to reboot into Windows</li>
</ol>
<h2 id="demo">Demo</h2>
<p><em>todo: film a video of the boot sequence :)</em></p>
    ]]></content:encoded>
</item>
<item>
<title>Domain-Driven Test Pipelines</title>
<link>https://unitehenry.com/blog/domain-driven-test-pipelines</link>
<description>An effective strategy for optimizing CI pipelines in large,
domain-driven projects by leveraging GitHub Actions matrix workflows to
run tests per domain (e.g., user, order, shipping) in parallel.</description>
<pubDate>Sat, 13 Sep 2025 00:00:00 +0000</pubDate>
<content:encoded><![CDATA[
      <!-- prettier-ignore -->
<h1 id="domain-driven-test-pipelines">Domain-Driven Test Pipelines</h1>
<p>2025-09-13</p>
<p>When building CI pipelines, a <a href="https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/run-job-variations">matrix
strategy</a> is a configuration setup that allows a CI system to run
multiple tests or build jobs in parallel with different combinations of
parameters. For example, building by operating systems, programming
language versions, or environment settings. Typically these are used to
run tests against multiple environments and versions of software. For
example, running python tests across different versions of python on
different operating systems.</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode yaml"><code class="sourceCode yaml"><span id="cb1-1"><a aria-hidden="true" href="#cb1-1" tabindex="-1"></a><span class="fu">jobs</span><span class="kw">:</span></span>
<span id="cb1-2"><a aria-hidden="true" href="#cb1-2" tabindex="-1"></a><span class="at">  </span><span class="fu">example_matrix</span><span class="kw">:</span></span>
<span id="cb1-3"><a aria-hidden="true" href="#cb1-3" tabindex="-1"></a><span class="at">    </span><span class="fu">strategy</span><span class="kw">:</span></span>
<span id="cb1-4"><a aria-hidden="true" href="#cb1-4" tabindex="-1"></a><span class="at">      </span><span class="fu">matrix</span><span class="kw">:</span></span>
<span id="cb1-5"><a aria-hidden="true" href="#cb1-5" tabindex="-1"></a><span class="at">        </span><span class="fu">python_version</span><span class="kw">:</span><span class="at"> </span><span class="kw">[</span><span class="dv">10</span><span class="kw">,</span><span class="at"> </span><span class="dv">12</span><span class="kw">,</span><span class="at"> </span><span class="dv">14</span><span class="kw">]</span></span>
<span id="cb1-6"><a aria-hidden="true" href="#cb1-6" tabindex="-1"></a><span class="at">        </span><span class="fu">os</span><span class="kw">:</span><span class="at"> </span><span class="kw">[</span><span class="at">ubuntu-latest</span><span class="kw">,</span><span class="at"> windows-latest</span><span class="kw">]</span></span></code></pre></div>
<p>In theory, a domain-driven project will have their tests logically
divided into these groupings as well.</p>
<pre class="plaintext"><code>com/example/api/
├── src/
│   ├── user/
│   │   ├── service/
│   │   ├── repository/
│   │   ├── controller/
│   ├── order/
│   │   ├── service/
│   │   ├── repository/
│   │   ├── controller/
│   ├── shipping/
│   │   ├── service/
│   │   ├── repository/
│   │   ├── controller/
├── test/
│   ├── user/
│   │   ├── service/
│   │   ├── controller/
│   ├── order/
│   │   ├── service/
│   │   ├── controller/
│   ├── shipping/
│   │   ├── service/
│   │   ├── controller/</code></pre>
<p>As your codebase grows and your tests grow with it, any tests that
require external dependencies may become resource constrained. For
example, attempting to run parallel tests against the same instance of
postgres. There are two bottlenecks that can occur in this situation: -
Limited amounts of tests can run in parallel - Limited database
connections can be opened and shared across tests</p>
<p>What if we leveraged the build matrix mechanism to run these tests in
parallel? Each domain would run it’s tests with it’s own DB instance and
in isolation of other domains.</p>
<h2 id="parameterizing-test-jobs">Parameterizing Test Jobs</h2>
<p>If you’re using <a href="https://docs.github.com/en/actions">GitHub
Actions</a>, it’s common to use <a href="https://docs.github.com/en/actions/how-tos/reuse-automations/reuse-workflows">reusable
workflows</a> to reuse existing snippets of steps to run your jobs. This
is especially useful when parameterizing the domain of tests that we
intend to run.</p>
<h3 id="gradle-example">Gradle Example</h3>
<div class="sourceCode" id="cb3"><pre class="sourceCode yaml"><code class="sourceCode yaml"><span id="cb3-1"><a aria-hidden="true" href="#cb3-1" tabindex="-1"></a><span class="co"># .github/workflows/test.yml</span></span>
<span id="cb3-2"><a aria-hidden="true" href="#cb3-2" tabindex="-1"></a></span>
<span id="cb3-3"><a aria-hidden="true" href="#cb3-3" tabindex="-1"></a><span class="fu">on</span><span class="kw">:</span></span>
<span id="cb3-4"><a aria-hidden="true" href="#cb3-4" tabindex="-1"></a><span class="at">  </span><span class="fu">workflow_call</span><span class="kw">:</span></span>
<span id="cb3-5"><a aria-hidden="true" href="#cb3-5" tabindex="-1"></a><span class="at">    </span><span class="fu">inputs</span><span class="kw">:</span></span>
<span id="cb3-6"><a aria-hidden="true" href="#cb3-6" tabindex="-1"></a><span class="at">      </span><span class="fu">domain</span><span class="kw">:</span></span>
<span id="cb3-7"><a aria-hidden="true" href="#cb3-7" tabindex="-1"></a><span class="at">        </span><span class="fu">required</span><span class="kw">:</span><span class="at"> </span><span class="ch">true</span></span>
<span id="cb3-8"><a aria-hidden="true" href="#cb3-8" tabindex="-1"></a><span class="at">        </span><span class="fu">type</span><span class="kw">:</span><span class="at"> string</span></span>
<span id="cb3-9"><a aria-hidden="true" href="#cb3-9" tabindex="-1"></a></span>
<span id="cb3-10"><a aria-hidden="true" href="#cb3-10" tabindex="-1"></a><span class="fu">jobs</span><span class="kw">:</span></span>
<span id="cb3-11"><a aria-hidden="true" href="#cb3-11" tabindex="-1"></a><span class="at">  </span><span class="fu">unit_test</span><span class="kw">:</span></span>
<span id="cb3-12"><a aria-hidden="true" href="#cb3-12" tabindex="-1"></a><span class="at">    </span><span class="fu">runs-on</span><span class="kw">:</span><span class="at"> depot-ubuntu-22.04</span></span>
<span id="cb3-13"><a aria-hidden="true" href="#cb3-13" tabindex="-1"></a><span class="at">    </span><span class="fu">steps</span><span class="kw">:</span></span>
<span id="cb3-14"><a aria-hidden="true" href="#cb3-14" tabindex="-1"></a><span class="at">      </span><span class="kw">-</span><span class="at"> </span><span class="fu">name</span><span class="kw">:</span><span class="at"> Checkout Repository</span></span>
<span id="cb3-15"><a aria-hidden="true" href="#cb3-15" tabindex="-1"></a><span class="at">        </span><span class="fu">uses</span><span class="kw">:</span><span class="at"> actions/checkout@v4</span></span>
<span id="cb3-16"><a aria-hidden="true" href="#cb3-16" tabindex="-1"></a><span class="at">        </span><span class="fu">with</span><span class="kw">:</span></span>
<span id="cb3-17"><a aria-hidden="true" href="#cb3-17" tabindex="-1"></a><span class="at">          </span><span class="fu">ref</span><span class="kw">:</span><span class="at"> ${{ github.head_ref }}</span></span>
<span id="cb3-18"><a aria-hidden="true" href="#cb3-18" tabindex="-1"></a></span>
<span id="cb3-19"><a aria-hidden="true" href="#cb3-19" tabindex="-1"></a><span class="at">      </span><span class="kw">-</span><span class="at"> </span><span class="fu">name</span><span class="kw">:</span><span class="at"> Setup Java 21</span></span>
<span id="cb3-20"><a aria-hidden="true" href="#cb3-20" tabindex="-1"></a><span class="at">        </span><span class="fu">uses</span><span class="kw">:</span><span class="at"> actions/setup-java@v4.5.0</span></span>
<span id="cb3-21"><a aria-hidden="true" href="#cb3-21" tabindex="-1"></a><span class="at">        </span><span class="fu">with</span><span class="kw">:</span></span>
<span id="cb3-22"><a aria-hidden="true" href="#cb3-22" tabindex="-1"></a><span class="at">          </span><span class="fu">distribution</span><span class="kw">:</span><span class="at"> </span><span class="st">'corretto'</span></span>
<span id="cb3-23"><a aria-hidden="true" href="#cb3-23" tabindex="-1"></a><span class="at">          </span><span class="fu">java-version</span><span class="kw">:</span><span class="at"> </span><span class="st">'21'</span></span>
<span id="cb3-24"><a aria-hidden="true" href="#cb3-24" tabindex="-1"></a></span>
<span id="cb3-25"><a aria-hidden="true" href="#cb3-25" tabindex="-1"></a><span class="at">      </span><span class="kw">-</span><span class="at"> </span><span class="fu">name</span><span class="kw">:</span><span class="at"> Setup Gradle</span></span>
<span id="cb3-26"><a aria-hidden="true" href="#cb3-26" tabindex="-1"></a><span class="at">        </span><span class="fu">uses</span><span class="kw">:</span><span class="at"> gradle/actions/setup-gradle@v4</span></span>
<span id="cb3-27"><a aria-hidden="true" href="#cb3-27" tabindex="-1"></a><span class="at">        </span><span class="fu">with</span><span class="kw">:</span></span>
<span id="cb3-28"><a aria-hidden="true" href="#cb3-28" tabindex="-1"></a><span class="at">          </span><span class="fu">gradle-version</span><span class="kw">:</span><span class="at"> </span><span class="st">'8.14.3'</span></span>
<span id="cb3-29"><a aria-hidden="true" href="#cb3-29" tabindex="-1"></a></span>
<span id="cb3-30"><a aria-hidden="true" href="#cb3-30" tabindex="-1"></a><span class="at">      </span><span class="kw">-</span><span class="at"> </span><span class="fu">name</span><span class="kw">:</span><span class="at"> Run Gradle Test</span></span>
<span id="cb3-31"><a aria-hidden="true" href="#cb3-31" tabindex="-1"></a><span class="at">        </span><span class="fu">run</span><span class="kw">:</span><span class="at"> ./gradlew test --tests='com.example.api.${{ inputs.domain }}.*'</span></span></code></pre></div>
<p>As an example, you may have a gradle project and a CI you want run
gradle tests on. Instead of running <code>./gradlew test</code> and
having your whole suite of tests run asynchronously top-to-bottom, we
can leverage a reusable workflow to run only a subset of tests based on
domain.</p>
<h2 id="domain-matrix">Domain Matrix</h2>
<div class="sourceCode" id="cb4"><pre class="sourceCode yaml"><code class="sourceCode yaml"><span id="cb4-1"><a aria-hidden="true" href="#cb4-1" tabindex="-1"></a><span class="co"># .github/workflows/feature</span></span>
<span id="cb4-2"><a aria-hidden="true" href="#cb4-2" tabindex="-1"></a></span>
<span id="cb4-3"><a aria-hidden="true" href="#cb4-3" tabindex="-1"></a><span class="fu">jobs</span><span class="kw">:</span></span>
<span id="cb4-4"><a aria-hidden="true" href="#cb4-4" tabindex="-1"></a><span class="at">  </span><span class="fu">test</span><span class="kw">:</span></span>
<span id="cb4-5"><a aria-hidden="true" href="#cb4-5" tabindex="-1"></a><span class="at">    </span><span class="fu">uses</span><span class="kw">:</span><span class="at"> .github/workflows/test.yml</span></span>
<span id="cb4-6"><a aria-hidden="true" href="#cb4-6" tabindex="-1"></a><span class="at">    </span><span class="fu">with</span><span class="kw">:</span></span>
<span id="cb4-7"><a aria-hidden="true" href="#cb4-7" tabindex="-1"></a><span class="at">      </span><span class="fu">domain</span><span class="kw">:</span><span class="at"> ${{ matrix.domain }}</span></span>
<span id="cb4-8"><a aria-hidden="true" href="#cb4-8" tabindex="-1"></a><span class="at">    </span><span class="fu">strategy</span><span class="kw">:</span></span>
<span id="cb4-9"><a aria-hidden="true" href="#cb4-9" tabindex="-1"></a><span class="at">      </span><span class="fu">matrix</span><span class="kw">:</span></span>
<span id="cb4-10"><a aria-hidden="true" href="#cb4-10" tabindex="-1"></a><span class="at">        </span><span class="fu">domain</span><span class="kw">:</span></span>
<span id="cb4-11"><a aria-hidden="true" href="#cb4-11" tabindex="-1"></a><span class="at">            </span><span class="kw">-</span><span class="at"> user</span></span>
<span id="cb4-12"><a aria-hidden="true" href="#cb4-12" tabindex="-1"></a><span class="at">            </span><span class="kw">-</span><span class="at"> order</span></span>
<span id="cb4-13"><a aria-hidden="true" href="#cb4-13" tabindex="-1"></a><span class="at">            </span><span class="kw">-</span><span class="at"> shipping</span></span></code></pre></div>
<p>Now that we have a reusable workflow for running our tests, we can
use a matrix strategy and run each domains set of tests in parallel of
each other.</p>
<h2 id="enforcing-domain-test-coverage">Enforcing Domain Test
Coverage</h2>
<div class="sourceCode" id="cb5"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb5-1"><a aria-hidden="true" href="#cb5-1" tabindex="-1"></a><span class="co">#! /usr/bin/env python</span></span>
<span id="cb5-2"><a aria-hidden="true" href="#cb5-2" tabindex="-1"></a></span>
<span id="cb5-3"><a aria-hidden="true" href="#cb5-3" tabindex="-1"></a><span class="im">import</span> os</span>
<span id="cb5-4"><a aria-hidden="true" href="#cb5-4" tabindex="-1"></a><span class="im">import</span> yaml</span>
<span id="cb5-5"><a aria-hidden="true" href="#cb5-5" tabindex="-1"></a></span>
<span id="cb5-6"><a aria-hidden="true" href="#cb5-6" tabindex="-1"></a><span class="kw">def</span> is_domain_covered(domain, test_domains):</span>
<span id="cb5-7"><a aria-hidden="true" href="#cb5-7" tabindex="-1"></a>  <span class="cf">for</span> test_domain <span class="kw">in</span> test_domains:</span>
<span id="cb5-8"><a aria-hidden="true" href="#cb5-8" tabindex="-1"></a>    <span class="cf">if</span> domain <span class="kw">in</span> test_domain:</span>
<span id="cb5-9"><a aria-hidden="true" href="#cb5-9" tabindex="-1"></a>      <span class="cf">return</span> <span class="va">True</span></span>
<span id="cb5-10"><a aria-hidden="true" href="#cb5-10" tabindex="-1"></a>  <span class="cf">return</span> <span class="va">False</span></span>
<span id="cb5-11"><a aria-hidden="true" href="#cb5-11" tabindex="-1"></a></span>
<span id="cb5-12"><a aria-hidden="true" href="#cb5-12" tabindex="-1"></a><span class="cf">if</span> <span class="va">__name__</span> <span class="op">==</span> <span class="st">'__main__'</span>:</span>
<span id="cb5-13"><a aria-hidden="true" href="#cb5-13" tabindex="-1"></a>  <span class="cf">with</span> <span class="bu">open</span>(<span class="st">'.github/workflows/feature.yml'</span>, <span class="st">'r'</span>) <span class="im">as</span> workflow_file:</span>
<span id="cb5-14"><a aria-hidden="true" href="#cb5-14" tabindex="-1"></a>    workflow <span class="op">=</span> yaml.load(workflow_file, Loader<span class="op">=</span>yaml.FullLoader)</span>
<span id="cb5-15"><a aria-hidden="true" href="#cb5-15" tabindex="-1"></a></span>
<span id="cb5-16"><a aria-hidden="true" href="#cb5-16" tabindex="-1"></a>    test_domains <span class="op">=</span> workflow[<span class="st">'jobs'</span>][<span class="st">'test'</span>][<span class="st">'strategy'</span>][<span class="st">'matrix'</span>][<span class="st">'domain'</span>]</span>
<span id="cb5-17"><a aria-hidden="true" href="#cb5-17" tabindex="-1"></a></span>
<span id="cb5-18"><a aria-hidden="true" href="#cb5-18" tabindex="-1"></a>    domains <span class="op">=</span> os.listdir(<span class="st">'com/example/src'</span>)</span>
<span id="cb5-19"><a aria-hidden="true" href="#cb5-19" tabindex="-1"></a></span>
<span id="cb5-20"><a aria-hidden="true" href="#cb5-20" tabindex="-1"></a>    <span class="cf">for</span> domain <span class="kw">in</span> domains:</span>
<span id="cb5-21"><a aria-hidden="true" href="#cb5-21" tabindex="-1"></a>      <span class="cf">if</span> <span class="kw">not</span> is_domain_covered(domain, test_domains):</span>
<span id="cb5-22"><a aria-hidden="true" href="#cb5-22" tabindex="-1"></a>        <span class="cf">raise</span> <span class="pp">Exception</span>(<span class="ss">f'The </span><span class="sc">{</span>domain<span class="sc">}</span><span class="ss"> domain is not covered in tests'</span>)</span></code></pre></div>
<p>What if we introduce a new domain to our project? Surely, we want
enforce that all domains are covered when we run our tests via a matrix
strategy. Here’s a python example of a simple bash script we can use to
parse the matrix strategy yaml array and verify it covers each domain
directory of our project.</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode yaml"><code class="sourceCode yaml"><span id="cb6-1"><a aria-hidden="true" href="#cb6-1" tabindex="-1"></a><span class="fu">jobs</span><span class="kw">:</span></span>
<span id="cb6-2"><a aria-hidden="true" href="#cb6-2" tabindex="-1"></a><span class="at">  </span><span class="fu">domain_coverage</span><span class="kw">:</span></span>
<span id="cb6-3"><a aria-hidden="true" href="#cb6-3" tabindex="-1"></a><span class="at">    </span><span class="fu">steps</span><span class="kw">:</span></span>
<span id="cb6-4"><a aria-hidden="true" href="#cb6-4" tabindex="-1"></a><span class="at">      </span><span class="kw">-</span><span class="at"> </span><span class="fu">name</span><span class="kw">:</span><span class="at"> Checkout Repository</span></span>
<span id="cb6-5"><a aria-hidden="true" href="#cb6-5" tabindex="-1"></a><span class="at">        </span><span class="fu">uses</span><span class="kw">:</span><span class="at"> actions/checkout@v4</span></span>
<span id="cb6-6"><a aria-hidden="true" href="#cb6-6" tabindex="-1"></a><span class="at">        </span><span class="fu">with</span><span class="kw">:</span></span>
<span id="cb6-7"><a aria-hidden="true" href="#cb6-7" tabindex="-1"></a><span class="at">          </span><span class="fu">ref</span><span class="kw">:</span><span class="at"> ${{ github.head_ref }}</span></span>
<span id="cb6-8"><a aria-hidden="true" href="#cb6-8" tabindex="-1"></a></span>
<span id="cb6-9"><a aria-hidden="true" href="#cb6-9" tabindex="-1"></a><span class="at">      </span><span class="kw">-</span><span class="at"> </span><span class="fu">name</span><span class="kw">:</span><span class="at"> Verify Domain Test Coverage</span></span>
<span id="cb6-10"><a aria-hidden="true" href="#cb6-10" tabindex="-1"></a><span class="at">        </span><span class="fu">run</span><span class="kw">:</span><span class="at"> ./scripts/test/domain_test_coverage.sh</span></span>
<span id="cb6-11"><a aria-hidden="true" href="#cb6-11" tabindex="-1"></a></span>
<span id="cb6-12"><a aria-hidden="true" href="#cb6-12" tabindex="-1"></a><span class="at">  </span><span class="fu">test</span><span class="kw">:</span></span>
<span id="cb6-13"><a aria-hidden="true" href="#cb6-13" tabindex="-1"></a><span class="at">    </span><span class="fu">needs</span><span class="kw">:</span><span class="at"> </span><span class="kw">[</span><span class="at"> domain_coverage </span><span class="kw">]</span></span>
<span id="cb6-14"><a aria-hidden="true" href="#cb6-14" tabindex="-1"></a><span class="at">    </span><span class="fu">uses</span><span class="kw">:</span><span class="at"> .github/workflows/unit-test.yml</span></span>
<span id="cb6-15"><a aria-hidden="true" href="#cb6-15" tabindex="-1"></a><span class="at">    </span><span class="fu">with</span><span class="kw">:</span></span>
<span id="cb6-16"><a aria-hidden="true" href="#cb6-16" tabindex="-1"></a><span class="at">      </span><span class="fu">domain</span><span class="kw">:</span><span class="at"> ${{ matrix.domain }}</span></span>
<span id="cb6-17"><a aria-hidden="true" href="#cb6-17" tabindex="-1"></a><span class="at">    </span><span class="fu">strategy</span><span class="kw">:</span></span>
<span id="cb6-18"><a aria-hidden="true" href="#cb6-18" tabindex="-1"></a><span class="at">      </span><span class="fu">matrix</span><span class="kw">:</span></span>
<span id="cb6-19"><a aria-hidden="true" href="#cb6-19" tabindex="-1"></a><span class="at">        </span><span class="fu">domain</span><span class="kw">:</span></span>
<span id="cb6-20"><a aria-hidden="true" href="#cb6-20" tabindex="-1"></a><span class="at">            </span><span class="kw">-</span><span class="at"> user</span></span>
<span id="cb6-21"><a aria-hidden="true" href="#cb6-21" tabindex="-1"></a><span class="at">            </span><span class="kw">-</span><span class="at"> order</span></span>
<span id="cb6-22"><a aria-hidden="true" href="#cb6-22" tabindex="-1"></a><span class="at">            </span><span class="kw">-</span><span class="at"> shipping</span></span></code></pre></div>
<p>Now we can run this bash script before the matrix strategy job runs,
to verify that all domains are covered with any incoming changes to our
project.</p>
<h2 id="omitting-irrelevant-domains-per-feature">Omitting Irrelevant
Domains Per Feature</h2>
<div class="sourceCode" id="cb7"><pre class="sourceCode yaml"><code class="sourceCode yaml"><span id="cb7-1"><a aria-hidden="true" href="#cb7-1" tabindex="-1"></a><span class="fu">on</span><span class="kw">:</span></span>
<span id="cb7-2"><a aria-hidden="true" href="#cb7-2" tabindex="-1"></a><span class="at">  </span><span class="fu">workflow_call</span><span class="kw">:</span></span>
<span id="cb7-3"><a aria-hidden="true" href="#cb7-3" tabindex="-1"></a><span class="at">    </span><span class="fu">inputs</span><span class="kw">:</span></span>
<span id="cb7-4"><a aria-hidden="true" href="#cb7-4" tabindex="-1"></a><span class="at">      </span><span class="fu">domain</span><span class="kw">:</span></span>
<span id="cb7-5"><a aria-hidden="true" href="#cb7-5" tabindex="-1"></a><span class="at">        </span><span class="fu">required</span><span class="kw">:</span><span class="at"> </span><span class="ch">true</span></span>
<span id="cb7-6"><a aria-hidden="true" href="#cb7-6" tabindex="-1"></a><span class="at">        </span><span class="fu">type</span><span class="kw">:</span><span class="at"> string</span></span>
<span id="cb7-7"><a aria-hidden="true" href="#cb7-7" tabindex="-1"></a></span>
<span id="cb7-8"><a aria-hidden="true" href="#cb7-8" tabindex="-1"></a><span class="fu">jobs</span><span class="kw">:</span></span>
<span id="cb7-9"><a aria-hidden="true" href="#cb7-9" tabindex="-1"></a><span class="at">  </span><span class="fu">unit_test</span><span class="kw">:</span></span>
<span id="cb7-10"><a aria-hidden="true" href="#cb7-10" tabindex="-1"></a><span class="at">    </span><span class="fu">runs-on</span><span class="kw">:</span><span class="at"> depot-ubuntu-22.04</span></span>
<span id="cb7-11"><a aria-hidden="true" href="#cb7-11" tabindex="-1"></a><span class="at">    </span><span class="fu">steps</span><span class="kw">:</span></span>
<span id="cb7-12"><a aria-hidden="true" href="#cb7-12" tabindex="-1"></a><span class="at">      </span><span class="kw">-</span><span class="at"> </span><span class="fu">name</span><span class="kw">:</span><span class="at"> Checkout Repository</span></span>
<span id="cb7-13"><a aria-hidden="true" href="#cb7-13" tabindex="-1"></a><span class="at">        </span><span class="fu">uses</span><span class="kw">:</span><span class="at"> actions/checkout@v4</span></span>
<span id="cb7-14"><a aria-hidden="true" href="#cb7-14" tabindex="-1"></a><span class="at">        </span><span class="fu">with</span><span class="kw">:</span></span>
<span id="cb7-15"><a aria-hidden="true" href="#cb7-15" tabindex="-1"></a><span class="at">          </span><span class="fu">ref</span><span class="kw">:</span><span class="at"> ${{ github.head_ref }}</span></span>
<span id="cb7-16"><a aria-hidden="true" href="#cb7-16" tabindex="-1"></a></span>
<span id="cb7-17"><a aria-hidden="true" href="#cb7-17" tabindex="-1"></a><span class="at">      </span><span class="kw">-</span><span class="at"> </span><span class="fu">name</span><span class="kw">:</span><span class="at"> Determine If Test Is Needed</span></span>
<span id="cb7-18"><a aria-hidden="true" href="#cb7-18" tabindex="-1"></a><span class="at">        </span><span class="fu">id</span><span class="kw">:</span><span class="at"> test_needed</span></span>
<span id="cb7-19"><a aria-hidden="true" href="#cb7-19" tabindex="-1"></a><span class="fu">        run</span><span class="kw">: </span><span class="ch">|</span></span>
<span id="cb7-20"><a aria-hidden="true" href="#cb7-20" tabindex="-1"></a>          git fetch origin main;</span>
<span id="cb7-21"><a aria-hidden="true" href="#cb7-21" tabindex="-1"></a></span>
<span id="cb7-22"><a aria-hidden="true" href="#cb7-22" tabindex="-1"></a>          INPUT_DOMAIN=$(echo '${{ inputs.domain }}' | cut -f 1 -d '.' -)</span>
<span id="cb7-23"><a aria-hidden="true" href="#cb7-23" tabindex="-1"></a></span>
<span id="cb7-24"><a aria-hidden="true" href="#cb7-24" tabindex="-1"></a>          if [ $(git diff origin/main --name-only | grep $INPUT_DOMAIN | wc | awk '{ print $1 }') -ne 0 ]; then</span>
<span id="cb7-25"><a aria-hidden="true" href="#cb7-25" tabindex="-1"></a>            echo "is-test-needed=true" &gt;&gt; "$GITHUB_OUTPUT";</span>
<span id="cb7-26"><a aria-hidden="true" href="#cb7-26" tabindex="-1"></a>          else</span>
<span id="cb7-27"><a aria-hidden="true" href="#cb7-27" tabindex="-1"></a>            # Run all tests when merged to main</span>
<span id="cb7-28"><a aria-hidden="true" href="#cb7-28" tabindex="-1"></a>            if [ $(git branch --show-current) == 'main' ]; then</span>
<span id="cb7-29"><a aria-hidden="true" href="#cb7-29" tabindex="-1"></a>              echo "is-test-needed=true" &gt;&gt; "$GITHUB_OUTPUT";</span>
<span id="cb7-30"><a aria-hidden="true" href="#cb7-30" tabindex="-1"></a>            else</span>
<span id="cb7-31"><a aria-hidden="true" href="#cb7-31" tabindex="-1"></a>              echo "is-test-needed=false" &gt;&gt; "$GITHUB_OUTPUT";</span>
<span id="cb7-32"><a aria-hidden="true" href="#cb7-32" tabindex="-1"></a>            fi</span>
<span id="cb7-33"><a aria-hidden="true" href="#cb7-33" tabindex="-1"></a>          fi</span>
<span id="cb7-34"><a aria-hidden="true" href="#cb7-34" tabindex="-1"></a></span>
<span id="cb7-35"><a aria-hidden="true" href="#cb7-35" tabindex="-1"></a><span class="at">      </span><span class="kw">-</span><span class="at"> </span><span class="fu">name</span><span class="kw">:</span><span class="at"> Setup Java 21</span></span>
<span id="cb7-36"><a aria-hidden="true" href="#cb7-36" tabindex="-1"></a><span class="at">        </span><span class="fu">if</span><span class="kw">:</span><span class="at"> steps.test_needed.outputs.is-test-needed == 'true'</span></span>
<span id="cb7-37"><a aria-hidden="true" href="#cb7-37" tabindex="-1"></a><span class="at">        </span><span class="fu">uses</span><span class="kw">:</span><span class="at"> actions/setup-java@v4.5.0</span></span>
<span id="cb7-38"><a aria-hidden="true" href="#cb7-38" tabindex="-1"></a><span class="at">        </span><span class="fu">with</span><span class="kw">:</span></span>
<span id="cb7-39"><a aria-hidden="true" href="#cb7-39" tabindex="-1"></a><span class="at">          </span><span class="fu">distribution</span><span class="kw">:</span><span class="at"> </span><span class="st">'corretto'</span></span>
<span id="cb7-40"><a aria-hidden="true" href="#cb7-40" tabindex="-1"></a><span class="at">          </span><span class="fu">java-version</span><span class="kw">:</span><span class="at"> </span><span class="st">'21'</span></span>
<span id="cb7-41"><a aria-hidden="true" href="#cb7-41" tabindex="-1"></a></span>
<span id="cb7-42"><a aria-hidden="true" href="#cb7-42" tabindex="-1"></a><span class="at">      </span><span class="kw">-</span><span class="at"> </span><span class="fu">name</span><span class="kw">:</span><span class="at"> Setup Gradle</span></span>
<span id="cb7-43"><a aria-hidden="true" href="#cb7-43" tabindex="-1"></a><span class="at">        </span><span class="fu">if</span><span class="kw">:</span><span class="at"> steps.test_needed.outputs.is-test-needed == 'true'</span></span>
<span id="cb7-44"><a aria-hidden="true" href="#cb7-44" tabindex="-1"></a><span class="at">        </span><span class="fu">uses</span><span class="kw">:</span><span class="at"> gradle/actions/setup-gradle@v4</span></span>
<span id="cb7-45"><a aria-hidden="true" href="#cb7-45" tabindex="-1"></a><span class="at">        </span><span class="fu">with</span><span class="kw">:</span></span>
<span id="cb7-46"><a aria-hidden="true" href="#cb7-46" tabindex="-1"></a><span class="at">          </span><span class="fu">gradle-version</span><span class="kw">:</span><span class="at"> </span><span class="st">'8.14.3'</span></span>
<span id="cb7-47"><a aria-hidden="true" href="#cb7-47" tabindex="-1"></a></span>
<span id="cb7-48"><a aria-hidden="true" href="#cb7-48" tabindex="-1"></a><span class="at">      </span><span class="kw">-</span><span class="at"> </span><span class="fu">name</span><span class="kw">:</span><span class="at"> Run Gradle Test</span></span>
<span id="cb7-49"><a aria-hidden="true" href="#cb7-49" tabindex="-1"></a><span class="at">        </span><span class="fu">if</span><span class="kw">:</span><span class="at"> steps.test_needed.outputs.is-test-needed == 'true'</span></span>
<span id="cb7-50"><a aria-hidden="true" href="#cb7-50" tabindex="-1"></a><span class="at">        </span><span class="fu">run</span><span class="kw">:</span><span class="at"> ./gradlew test --tests='com.example.api.${{ inputs.domain }}.*'</span></span></code></pre></div>
<p>Now that we’re running tests based on domain, we can conditionally
run tests for features that might not impact other domains. By checking
the files included in a <code>git diff</code>, we can check if the
<code>inputs.domain</code> is included with the files being changed, and
only then run tests as needed.</p>
<p>This is a great way to reduce billable CI minutes.</p>
<p><strong>Note:</strong> You still will likely want to run all tests
after the feature is merged.</p>
<h2 id="benefits">Benefits</h2>
<div class="sourceCode" id="cb8"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb8-1"><a aria-hidden="true" href="#cb8-1" tabindex="-1"></a><span class="ex">$</span> gradle test <span class="at">--parallel</span></span></code></pre></div>
<p>Given the gradle example earlier, build tools might have parallel
testing mechanisms like <a href="https://docs.gradle.org/current/userguide/performance.html#sec:enable_parallel_execution"><code>--parallel</code></a>.
These very well may work out of the box for different projects, but some
issues I’ve encountered in the past when leveraging these mechanisms
are: - External dependencies be constrained to run the tests in parallel
- Threads used for tests constrained by the amount of CPU cores the
runners have</p>
<p>When parallelizing tests by project domain, here are some of the
benefits that these changes yield:</p>
<ul>
<li>Physical isolation of resources and domains per set of tests,
reducing resource constraints like database connections</li>
<li>Run more tests in parallel, reducing the time it takes for
developers to get feedback on their features</li>
<li>Less tests ran on a per feature basis, reducing billable CI
minutes</li>
</ul>
    ]]></content:encoded>
</item>
<item>
<title>AI (2025)</title>
<link>https://unitehenry.com/blog/ai-2025</link>
<description>My thoughts on AI in the year 2025.</description>
<pubDate>Sat, 19 Apr 2025 00:00:00 +0000</pubDate>
<content:encoded><![CDATA[
      <!-- prettier-ignore -->
<h1 id="ai-2025">AI (2025)</h1>
<p>2025-04-19</p>
<p>In the year 2025, here are some of my thoughts around the influence
of “AI” in the world of software development.</p>
<h2 id="machine-learning">Machine Learning</h2>
<p>AI is a marketing term; machine learning is a technology.</p>
<p>AI is a marketing term you slap onto a product, or company, that is
strapping the ChatGPT API onto a legacy system or fork of VS Code.</p>
<h2 id="writing-code">Writing Code</h2>
<p>A software engineer is not limited by the amount of code they can
write. If they are, they’re a bad software engineer. The skill involved
with software engineering revolves around cutting scope, deleting
requirements, and building systems that integrate with other
systems.</p>
<p>That’s not to say AI won’t be able to do this in the future. But
that’s <em>really</em> far away.</p>
<h2 id="predicting">Predicting</h2>
<p>An LLM works by stringing together symbols probabilistically from
information that already exists. The vast majority of the code you write
shouldn’t be able to be generated this way, and if it is, you’re
probably not working on anything interesting or useful.</p>
<p>This section was inspired by a <a href="https://www.youtube.com/watch?v=c1TTqHUxIF8">Jonathan Blow
rant</a>.</p>
<h2 id="layoffs">Layoffs</h2>
<p>When a company lays off employees due to “efficiencies with AI” or
“AI is replacing software engineers,” that’s just an excuse to layoff
people in their organization who weren’t adding value to begin with.</p>
<h2 id="mediocre-engineers">Mediocre Engineers</h2>
<p>AI makes mediocre engineers less mediocre. Personally, I have not
found a single useful AI tool that I can add to my workflow to make me
even a 1% more productive.</p>
<p>Copilots and AI editors allow mediocre engineers to abandon first
principles* and write shit code.</p>
<p><em>*as if they ever had first principles to begin with.</em></p>
<h2 id="a-generation-of-software-engineers">A Generation of Software
Engineers</h2>
<p>I think we might be on the verge of loosing a generation of software
engineers with first principles knowledge, especially domestically in
the United States.</p>
<p>The same way we take hardware, operating systems, compilers, etc. for
granted - I believe this effect is working its way up the software stack
through means of AI.</p>
    ]]></content:encoded>
</item>
<item>
<title>Working Hard</title>
<link>https://unitehenry.com/blog/working-hard</link>
<description>Some concepts around my work ethic that I’ve learned over the
years.</description>
<pubDate>Sun, 23 Jun 2024 00:00:00 +0000</pubDate>
<content:encoded><![CDATA[
      <!-- prettier-ignore -->
<h1 id="working-hard">Working Hard</h1>
<p>2024-06-23</p>
<p><em>The topics in this article are tangential to each other. But I
just wanted to write down some concepts around my work ethic that I’ve
learned over the years.</em></p>
<p><em>I might spin some of these topics out into their own article in
the future.</em></p>
<hr>
<p>I used to be the kind of person who would work all day and all night
out of “survival”, or at least that is what I would convince myself. It
was a mentality that if I wasn’t coding, I could lose it all at any
moment.</p>
<h2 id="impostor-syndrome">Impostor Syndrome</h2>
<p>One of the reasons I felt the need to work at every waking minute,
was because I never finished a traditional four-year degree. I convinced
myself that because my peers had a degree in computer science, I would
have to work twice as hard to prove that I belonged at whatever job I
was working at.</p>
<p>It wasn’t until five years into my career that I realized degrees,
universities, and the companies you used to work at don’t matter. The
only thing that matters is if you have the skills to make a big impact
and deliver value.</p>
<p>All that matters is if you can <a href="#focus-on-moving-the-needle">move the needle</a>.</p>
<h2 id="focus-on-moving-the-needle">Focus On Moving The Needle</h2>
<p>I have three principles that help guide me in making sure that I’m
“moving the needle.”</p>
<h3 id="pick-up-the-phone">Pick Up The Phone</h3>
<p>You should always be talking to your customers. A customer can be
more than just an end user; they could be a stakeholder, an engineer, or
a product manager.</p>
<p>This guiding principle is used to ensure that you are always working
on the right thing. Don’t wait for answers, or make assumptions; pick up
the phone, and call the person who is expecting the deliverable you’re
working on.</p>
<p>The skill here is not talking to customers. It is the ability to
delete requirements.</p>
<h3 id="make-an-impact">Make An Impact</h3>
<p>The goal here is to always make sure you’re working on the right
thing, and then actually doing the thing (aka writing code).</p>
<p>How do you know if you’re working on the right thing? <a href="#pick-up-the-phone">Pick up the phone</a> and ask your
customer!</p>
<p>You should only be <em>working</em> on things that make an
impact.</p>
<h3 id="deliver-value">Deliver Value</h3>
<p>High impact, high value; when you actually put the final product in
the customer’s hands, measure the value it delivers.</p>
<p>There are two reasons why you want to do this: - You actually see how
impactful the thing you worked on was. - You have tangible data to
validate that you are the right person for the job.</p>
<p>When you deliver value, the needle moves.</p>
<h2 id="zoom-out">Zoom Out</h2>
<p>I used to think everything had to be done in a single day. What I’ve
learned is that it’s important to set milestones.</p>
<p>Instead of tunnel visioning and heads down coding, set a goal like:
“by the end of the week, we will have this one thing done.” It is the
typical “under promise, over deliver.” But I treat it as room to
maneuver. It doesn’t matter how I get there, as long as I deliver it by
the time all the stakeholders agreed upon.</p>
<p>This is a tool to help me focus on <a href="#focus-on-moving-the-needle">moving the needle</a>.</p>
<h2 id="in-person-work">In-Person Work</h2>
<p>The way I look at in-person work, especially when it comes to
startups, is that if you’re going to build something that is going to
make an impact in the world, it’s not going to happen over a zoom
call.</p>
<p>My opinion: the iPhone could not of been built over a zoom call.</p>
<p>It’s not about being able to get more work done. Yes, you’re likely
to get more work done in the isolation of your home.</p>
<p>It’s about ensuring that you’re always working on the right thing.
When people are working in a room together, it’s much easier to talk
about what everyone is working on and requirements that can be
deleted!</p>
<p>It gives you the ability to always be asking: are we working on the
right thing?</p>
<h2 id="waking-up-early-vs-staying-up-late">Waking Up Early vs Staying
Up Late</h2>
<p>TLDR; whatever works for you.</p>
<p>If you feel like it’s easier to stay up late and be productive, do
that. If you find yourself tired the next day because you decided to
stay up late, cool; you can just get even better sleep the following
night!</p>
<p>If you feel like waking up early makes you more productive, do
that.</p>
<p>Again, all that matters is that you <a href="#focus-on-moving-the-needle">focus on moving the needle</a>.</p>
    ]]></content:encoded>
</item>
<item>
<title>Money (25)</title>
<link>https://unitehenry.com/blog/money-25</link>
<description>How I think about money at 25 years old.</description>
<pubDate>Fri, 07 Jun 2024 00:00:00 +0000</pubDate>
<content:encoded><![CDATA[
      <!-- prettier-ignore -->
<h1 id="money-25">Money (25)</h1>
<p>2024-06-07</p>
<p>This is how I (25 years old and broke*) currently think about
money.</p>
<h2 id="die-dirt-poor-or-die-super-rich">Die Dirt Poor, Or Die Super
Rich</h2>
<blockquote>
<p>Get Rich or Die Tryin’ – Curtis “50 Cent” Jackson</p>
</blockquote>
<p>I don’t really care which way it goes: if I die dirt poor, or if I
die super rich.</p>
<p>I would prefer dying rich. But if I die poor, I just want to make
sure I die doing something that brought value to others.</p>
<h2 id="investing">Investing</h2>
<blockquote>
<p>The best investment by far is anything that develops yourself, and
it’s not taxed at all…Whatever abilities you have can’t be taken away
from you. They can’t actually be inflated away from you. – Warren
Buffet</p>
</blockquote>
<p>Here is a breakdown of my portfolio: - My current skills and the
skills I’m developing - Money saved to build something valuable to
people - <a href="#social-capital">Social capital</a></p>
<p>I’m leaving all my chips on the table.</p>
<p>All the money I make goes into building something of value in the
world.</p>
<p>If I run out of money, I will <a href="#cut-your-burn">cut my
burn</a> and make more so I can do it again.</p>
<h2 id="dont-own-things">Don’t Own Things</h2>
<blockquote>
<p>Until we have begun to go without them, we fail to realize how
unnecessary many things are. We’ve been using them not because we needed
them but because we had them. – Seneca</p>
</blockquote>
<p>Here is a list of my material possessions:</p>
<ul>
<li>Phone</li>
<li>Laptop</li>
<li>Clothes</li>
</ul>
<p>Material possessions are just liabilities in life that burn your
money. You have to insure them. You have to perform maintenance on them.
They widen your exposure to risk. You have to consider traveling with
them, and they weigh you down.</p>
<p>Personally owning a house or a car is a scam. They are just ways to
expose your life to economic conditions. The only place you
<em>need</em> a house or a car is the suburbs.</p>
<p><strong>Do not live in the suburbs!</strong></p>
<p>You should live in a city, or a farm.</p>
<p>I personally do the former. A city is a great place to live because
there are jobs, there are ways to commute to jobs without a car, and
there are ways to live cheap and ways to live not-cheap depending on
your circumstances.</p>
<p>I keep all my belongings down to a single suitcase. I do this so that
I can pick up and move at any moment. I find it important to be ready
when opportunity comes knocking on your front door.</p>
<p>When I was fed-up with my job in Miami and there were not
opportunities in that region during those particular market conditions,
I was able to pick up and move to San Francisco where opportunity was
plentiful.</p>
<h2 id="dont-want-things">Don’t Want Things</h2>
<blockquote>
<p>No person has the power to have everything they want, but it is in
their power not to want what they don’t have, and to cheerfully put to
good use what they do possess. – Seneca</p>
</blockquote>
<p>TLDR; If you want things, you’ll spend your money on bullshit.</p>
<p>Here are the things I want in life: - To love my girlfriend. - To
build something that people get value out of.</p>
<p>As for everything else in life, all I say is: fuck off and leave me
alone.</p>
<p>That being said, we are people, and there are things that are nice to
have.</p>
<p>Here are the things that I find nice to have:</p>
<ul>
<li>Designer hoodies</li>
<li>Dining at nice restaurants</li>
<li>Living in a nice condo with amenities</li>
</ul>
<p>These are things that I don’t care if I have or not, but I’ll spend
my money on if it’s within my means.</p>
<h2 id="short-term-living">Short Term Living</h2>
<p>I typically will live somewhere (like an Airbnb) for a month, or two,
at a time.</p>
<p>I would rather pay more money and not be obligated to staying in one
place, then to pay less money over a longer period of time.</p>
<p>I want to mitigate my exposure to risk in different economic
conditions. So if it is harder to find a job in a particular market
condition, I have two options: - Move to a cheaper place to live and <a href="#cut-your-burn">cut my burn</a> - Move to a place with better
opportunities</p>
<p>The last position I want to be in: is one where I have a 12-month
lease and the market dictates that I can’t continue to afford it halfway
through.</p>
<p>Another reason short-term living is great, is because I don’t need to
buy furniture. Furniture is another material possession (like cars and
houses) that weigh you down.</p>
<h2 id="eating-out">Eating Out</h2>
<blockquote>
<p>We should eat to live, not live to eat. – Socrates</p>
</blockquote>
<p>I buy coffee everyday; sometimes I buy it two, or three, times a
day.</p>
<p>I think it’s important to call out because of the typical “saving $5
on coffee at Starbucks in the morning” saying.</p>
<p>When I was single, I would order Uber Eats every day because it was
convenient.</p>
<p>If something makes my life more comfortable living, and it’s within
my means to afford it, then I will just buy it.</p>
<h2 id="social-capital">Social Capital</h2>
<blockquote>
<p>Giving is in essence a flow of your loving self to others. When that
happens, it sets up a flow of supportive energy back to you. When we say
you must give to receive, it’s not that you give a thing in order to get
something. Rather it’s a way of explaining how the abundance of life
flows. If you are not feeling that flow of abundance coming to you, then
the first thing to ask is if you are creating a flow of giving from
yourself. You don’t need to manipulate or control your giving. Simply
give what you can with a full heart. And don’t try to control what you
will be receiving either. Know that you will automatically receive
exactly what you need. – Deepak Chopra</p>
</blockquote>
<p>It is important to invest in good people that surround you.</p>
<p>It is intangible, and hard to judge when to do, but I enjoy buying
gifts and treating people to nice dinners that have done right by
me.</p>
<p>It is a great way to spread joy, show appreciation, and plant seeds
of favor that compound into the future. I don’t count my returns, and I
don’t keep score.</p>
<h2 id="pay-your-taxes">Pay Your Taxes</h2>
<blockquote>
<p>Nothing will ever befall me that I will receive with gloom or a bad
disposition. I will pay my taxes gladly. Now, all the things which cause
complaint or dread are like the taxes of life—things from which, my dear
Lucilius, you should never hope for exemption or seek escape. –
Seneca</p>
</blockquote>
<blockquote>
<p>Mo’ Money Mo’ Problems – Christopher “The Notorious B.I.G.”
Wallace</p>
</blockquote>
<p>I don’t know who said it better: Biggie or Seneca. But the point is
the same. The more successful you are and the more responsibility you
have, come with taxes.</p>
<p>Marrying your girlfriend comes with the tax of buying a ring.</p>
<p>Surrounding yourself with successful people comes with the tax of
doing expensive activities together like going out to dinner.</p>
<p>There are things in life you just have to pay for whether you like it
or not, but usually it’s because you’re in a fortunate position of
having the leftover money to do it. So be grateful, and just pay your
taxes.</p>
<h2 id="cut-your-burn">Cut Your Burn</h2>
<p><strong>Burn</strong> – The money that is necessary to spend every
month to survive, over the money you have saved plus income.</p>
<p>For the most part, all my money goes to two major categories: rent
and food.</p>
<p>Since these are the two major categories of my spend, then I have two
ways to cut my burn:</p>
<ul>
<li><a href="#short-term-living">Move to a cheaper place to live
(roommates if necessary)</a></li>
<li><a href="#eating-out">Don’t go out to eat or get food
delivered</a></li>
</ul>
<p>Another tip to cutting your burn, is just don’t leave the house. If
you’re not leaving the house, you’re not burning money
unnecessarily.</p>
<h2 id="endgame">Endgame</h2>
<p>The last rule that I follow when it comes to money: is just don’t
spend more than you make.</p>
<p>The theme of my philosophy on money: is just focus on doing something
you love and delivers value to other people. Everything else is just
noise.</p>
<p><em>* I’m broke, but I’m hustling.</em></p>
    ]]></content:encoded>
</item>
<item>
<title>What’s in My RC 2022</title>
<link>https://unitehenry.com/blog/whats-in-my-rc-2022</link>
<description>A tour of my RC file in 2022.</description>
<pubDate>Mon, 19 Dec 2022 00:00:00 +0000</pubDate>
<content:encoded><![CDATA[
      <!-- prettier-ignore -->
<h1 id="whats-in-my-rc-2022">What’s in My RC 2022</h1>
<p>2022-12-19</p>
<p>Welcome to a tour of my <code>.zshrc</code> file! I thought it would
be fun to take a dive into the different tools I use to elevate my
developer game.</p>
<p>If you want to see my whole config, I keep all my setup files on
GitHub: <a href="https://github.com/unitehenry/config">https://github.com/unitehenry/config</a></p>
<p>Also check out last years: <a href="/blog/whats-in-my-rc-2021">What’s
in my RC 2021</a></p>
<h1 id="the-variables">The Variables</h1>
<div class="sourceCode" id="cb1"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a aria-hidden="true" href="#cb1-1" tabindex="-1"></a><span class="co"># EDITOR</span></span>
<span id="cb1-2"><a aria-hidden="true" href="#cb1-2" tabindex="-1"></a><span class="bu">export</span> <span class="va">EDITOR</span><span class="op">=</span><span class="st">"vi"</span><span class="kw">;</span></span>
<span id="cb1-3"><a aria-hidden="true" href="#cb1-3" tabindex="-1"></a><span class="bu">export</span> <span class="va">VISUAL</span><span class="op">=</span><span class="st">"vi"</span><span class="kw">;</span></span>
<span id="cb1-4"><a aria-hidden="true" href="#cb1-4" tabindex="-1"></a></span>
<span id="cb1-5"><a aria-hidden="true" href="#cb1-5" tabindex="-1"></a><span class="co"># iCloud Directory</span></span>
<span id="cb1-6"><a aria-hidden="true" href="#cb1-6" tabindex="-1"></a><span class="bu">export</span> <span class="va">DOCS</span><span class="op">=</span><span class="st">"/Users/</span><span class="va">$(</span><span class="fu">whoami</span><span class="va">)</span><span class="st">/Library/Mobile Documents/com~apple~CloudDocs"</span><span class="kw">;</span></span>
<span id="cb1-7"><a aria-hidden="true" href="#cb1-7" tabindex="-1"></a></span>
<span id="cb1-8"><a aria-hidden="true" href="#cb1-8" tabindex="-1"></a><span class="co"># Work Directory</span></span>
<span id="cb1-9"><a aria-hidden="true" href="#cb1-9" tabindex="-1"></a><span class="bu">export</span> <span class="va">WORK</span><span class="op">=</span><span class="st">"/Users/</span><span class="va">$(</span><span class="fu">whoami</span><span class="va">)</span><span class="st">/Projects/lula"</span><span class="kw">;</span></span>
<span id="cb1-10"><a aria-hidden="true" href="#cb1-10" tabindex="-1"></a></span>
<span id="cb1-11"><a aria-hidden="true" href="#cb1-11" tabindex="-1"></a><span class="co"># Vault Address</span></span>
<span id="cb1-12"><a aria-hidden="true" href="#cb1-12" tabindex="-1"></a><span class="bu">export</span> <span class="va">VAULT_ADDR</span><span class="op">=</span>https://vault.stallions.dev/</span></code></pre></div>
<ul>
<li>Continuing to use vim, so just exporting my preferred editor
variables</li>
<li>Reference my iCloud directory which I use to keep all my personal
files</li>
<li>Adding an environment variable to help reference <a href="https://www.vaultproject.io/">where my company is storing
secrets</a></li>
</ul>
<h1 id="credentials">Credentials</h1>
<div class="sourceCode" id="cb2"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb2-1"><a aria-hidden="true" href="#cb2-1" tabindex="-1"></a><span class="co"># Credentials Fetcher</span></span>
<span id="cb2-2"><a aria-hidden="true" href="#cb2-2" tabindex="-1"></a><span class="kw">function</span><span class="fu"> op-create()</span> <span class="kw">{</span></span>
<span id="cb2-3"><a aria-hidden="true" href="#cb2-3" tabindex="-1"></a>  <span class="ex">op</span> item template get Login <span class="op">&gt;</span> /tmp/login.json<span class="kw">;</span></span>
<span id="cb2-4"><a aria-hidden="true" href="#cb2-4" tabindex="-1"></a>  <span class="cf">if</span> <span class="bu">[</span> <span class="ot">-n</span> <span class="st">"</span><span class="va">$3</span><span class="st">"</span> <span class="bu">]</span></span>
<span id="cb2-5"><a aria-hidden="true" href="#cb2-5" tabindex="-1"></a>  <span class="cf">then</span></span>
<span id="cb2-6"><a aria-hidden="true" href="#cb2-6" tabindex="-1"></a>    <span class="bu">echo</span> <span class="va">$(</span><span class="fu">cat</span> /tmp/login.json <span class="kw">|</span> <span class="ex">jq</span> <span class="at">-r</span> <span class="at">-c</span> <span class="st">"(.fields[] | select(.id | contains(</span><span class="dt">\"</span><span class="st">username</span><span class="dt">\"</span><span class="st">))) .value = </span><span class="dt">\"</span><span class="va">$2</span><span class="dt">\"</span><span class="st">"</span><span class="va">)</span> <span class="op">&gt;</span> /tmp/login.json<span class="kw">;</span></span>
<span id="cb2-7"><a aria-hidden="true" href="#cb2-7" tabindex="-1"></a>    <span class="bu">echo</span> <span class="va">$(</span><span class="fu">cat</span> /tmp/login.json <span class="kw">|</span> <span class="ex">jq</span> <span class="at">-r</span> <span class="at">-c</span> <span class="st">"(.fields[] | select(.id | contains(</span><span class="dt">\"</span><span class="st">password</span><span class="dt">\"</span><span class="st">))) .value = </span><span class="dt">\"</span><span class="va">$3</span><span class="dt">\"</span><span class="st">"</span><span class="va">)</span> <span class="op">&gt;</span> /tmp/login.json<span class="kw">;</span></span>
<span id="cb2-8"><a aria-hidden="true" href="#cb2-8" tabindex="-1"></a>    <span class="ex">op</span> item create <span class="at">--template</span> /tmp/login.json <span class="at">--title</span> <span class="va">$1</span><span class="kw">;</span></span>
<span id="cb2-9"><a aria-hidden="true" href="#cb2-9" tabindex="-1"></a>  <span class="cf">else</span></span>
<span id="cb2-10"><a aria-hidden="true" href="#cb2-10" tabindex="-1"></a>    <span class="bu">echo</span> <span class="va">$(</span><span class="fu">cat</span> /tmp/login.json <span class="kw">|</span> <span class="ex">jq</span> <span class="at">-r</span> <span class="at">-c</span> <span class="st">"(.fields[] | select(.id | contains(</span><span class="dt">\"</span><span class="st">username</span><span class="dt">\"</span><span class="st">))) .value = </span><span class="dt">\"</span><span class="va">$2</span><span class="dt">\"</span><span class="st">"</span><span class="va">)</span> <span class="op">&gt;</span> /tmp/login.json<span class="kw">;</span></span>
<span id="cb2-11"><a aria-hidden="true" href="#cb2-11" tabindex="-1"></a>    <span class="ex">op</span> item create <span class="at">--template</span> /tmp/login.json <span class="at">--title</span> <span class="va">$1</span> <span class="at">--generate-password</span><span class="kw">;</span></span>
<span id="cb2-12"><a aria-hidden="true" href="#cb2-12" tabindex="-1"></a>  <span class="cf">fi</span></span>
<span id="cb2-13"><a aria-hidden="true" href="#cb2-13" tabindex="-1"></a>  <span class="fu">rm</span> /tmp/login.json<span class="kw">;</span></span>
<span id="cb2-14"><a aria-hidden="true" href="#cb2-14" tabindex="-1"></a><span class="kw">}</span></span>
<span id="cb2-15"><a aria-hidden="true" href="#cb2-15" tabindex="-1"></a></span>
<span id="cb2-16"><a aria-hidden="true" href="#cb2-16" tabindex="-1"></a><span class="kw">function</span><span class="fu"> op-list()</span> <span class="kw">{</span></span>
<span id="cb2-17"><a aria-hidden="true" href="#cb2-17" tabindex="-1"></a>  <span class="ex">op</span> item list <span class="at">--format</span><span class="op">=</span>json <span class="kw">|</span> <span class="ex">jq</span> <span class="at">-c</span> <span class="at">-r</span> <span class="st">'.[].title'</span><span class="kw">;</span></span>
<span id="cb2-18"><a aria-hidden="true" href="#cb2-18" tabindex="-1"></a><span class="kw">}</span></span>
<span id="cb2-19"><a aria-hidden="true" href="#cb2-19" tabindex="-1"></a></span>
<span id="cb2-20"><a aria-hidden="true" href="#cb2-20" tabindex="-1"></a><span class="kw">function</span><span class="fu"> op-username()</span> <span class="kw">{</span></span>
<span id="cb2-21"><a aria-hidden="true" href="#cb2-21" tabindex="-1"></a> <span class="ex">op</span> item get <span class="va">$@</span> <span class="at">--format</span><span class="op">=</span>json <span class="kw">|</span> <span class="ex">jq</span> <span class="at">-c</span> <span class="at">-r</span> <span class="st">'.fields[] | select(.id | contains("username")) | .value'</span><span class="kw">;</span></span>
<span id="cb2-22"><a aria-hidden="true" href="#cb2-22" tabindex="-1"></a><span class="kw">}</span></span>
<span id="cb2-23"><a aria-hidden="true" href="#cb2-23" tabindex="-1"></a></span>
<span id="cb2-24"><a aria-hidden="true" href="#cb2-24" tabindex="-1"></a><span class="kw">function</span><span class="fu"> op-password()</span> <span class="kw">{</span></span>
<span id="cb2-25"><a aria-hidden="true" href="#cb2-25" tabindex="-1"></a>  <span class="ex">op</span> item get <span class="va">$@</span> <span class="at">--format</span><span class="op">=</span>json <span class="kw">|</span> <span class="ex">jq</span> <span class="at">-c</span> <span class="at">-r</span> <span class="st">'.fields[] | select(.id | contains("password")) | .value'</span><span class="kw">;</span></span>
<span id="cb2-26"><a aria-hidden="true" href="#cb2-26" tabindex="-1"></a><span class="kw">}</span></span>
<span id="cb2-27"><a aria-hidden="true" href="#cb2-27" tabindex="-1"></a></span>
<span id="cb2-28"><a aria-hidden="true" href="#cb2-28" tabindex="-1"></a><span class="kw">function</span><span class="fu"> op-delete()</span> <span class="kw">{</span></span>
<span id="cb2-29"><a aria-hidden="true" href="#cb2-29" tabindex="-1"></a>  <span class="ex">op</span> item delete <span class="va">$@</span><span class="kw">;</span></span>
<span id="cb2-30"><a aria-hidden="true" href="#cb2-30" tabindex="-1"></a><span class="kw">}</span></span></code></pre></div>
<p>I used to be anti hosted password managers, but I was recommended
using 1password and their <a href="https://developer.1password.com/docs/cli">CLI client</a> to manage
my passwords.</p>
<h1 id="code-formatter">Code Formatter</h1>
<div class="sourceCode" id="cb3"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb3-1"><a aria-hidden="true" href="#cb3-1" tabindex="-1"></a><span class="co">## Code Formatter</span></span>
<span id="cb3-2"><a aria-hidden="true" href="#cb3-2" tabindex="-1"></a><span class="kw">function</span><span class="fu"> format-file()</span> <span class="kw">{</span></span>
<span id="cb3-3"><a aria-hidden="true" href="#cb3-3" tabindex="-1"></a>  <span class="bu">export</span> <span class="va">FILENAME</span><span class="op">=</span><span class="st">"</span><span class="va">$(</span><span class="fu">basename</span> <span class="va">$@)</span><span class="st">"</span><span class="kw">;</span></span>
<span id="cb3-4"><a aria-hidden="true" href="#cb3-4" tabindex="-1"></a>  <span class="bu">export</span> <span class="va">EXTENSION</span><span class="op">=</span><span class="st">"</span><span class="va">${FILENAME</span><span class="op">##</span><span class="pp">*</span>.<span class="va">}</span><span class="st">"</span><span class="kw">;</span></span>
<span id="cb3-5"><a aria-hidden="true" href="#cb3-5" tabindex="-1"></a></span>
<span id="cb3-6"><a aria-hidden="true" href="#cb3-6" tabindex="-1"></a>  <span class="cf">if</span> <span class="bu">[</span> <span class="va">$EXTENSION</span> <span class="ot">=</span> <span class="st">'py'</span> <span class="bu">]</span></span>
<span id="cb3-7"><a aria-hidden="true" href="#cb3-7" tabindex="-1"></a>  <span class="cf">then</span></span>
<span id="cb3-8"><a aria-hidden="true" href="#cb3-8" tabindex="-1"></a>    <span class="ex">yapf</span> <span class="at">--in-place</span> <span class="va">$@</span><span class="kw">;</span></span>
<span id="cb3-9"><a aria-hidden="true" href="#cb3-9" tabindex="-1"></a>    <span class="cf">return</span> <span class="dv">0</span><span class="kw">;</span></span>
<span id="cb3-10"><a aria-hidden="true" href="#cb3-10" tabindex="-1"></a>  <span class="cf">fi</span></span>
<span id="cb3-11"><a aria-hidden="true" href="#cb3-11" tabindex="-1"></a></span>
<span id="cb3-12"><a aria-hidden="true" href="#cb3-12" tabindex="-1"></a>  <span class="cf">if</span> <span class="bu">[</span> <span class="va">$EXTENSION</span> <span class="ot">=</span> <span class="st">'php'</span> <span class="bu">]</span></span>
<span id="cb3-13"><a aria-hidden="true" href="#cb3-13" tabindex="-1"></a>  <span class="cf">then</span></span>
<span id="cb3-14"><a aria-hidden="true" href="#cb3-14" tabindex="-1"></a>    <span class="ex">php-cs-fixer</span> fix <span class="va">$@</span><span class="kw">;</span></span>
<span id="cb3-15"><a aria-hidden="true" href="#cb3-15" tabindex="-1"></a>    <span class="fu">rm</span> .php_cs.cache<span class="kw">;</span></span>
<span id="cb3-16"><a aria-hidden="true" href="#cb3-16" tabindex="-1"></a>    <span class="cf">return</span> <span class="dv">0</span><span class="kw">;</span></span>
<span id="cb3-17"><a aria-hidden="true" href="#cb3-17" tabindex="-1"></a>  <span class="cf">fi</span></span>
<span id="cb3-18"><a aria-hidden="true" href="#cb3-18" tabindex="-1"></a></span>
<span id="cb3-19"><a aria-hidden="true" href="#cb3-19" tabindex="-1"></a>  <span class="cf">if</span> <span class="bu">[</span> <span class="va">$EXTENSION</span> <span class="ot">=</span> <span class="st">'sql'</span> <span class="bu">]</span></span>
<span id="cb3-20"><a aria-hidden="true" href="#cb3-20" tabindex="-1"></a>  <span class="cf">then</span></span>
<span id="cb3-21"><a aria-hidden="true" href="#cb3-21" tabindex="-1"></a>    <span class="ex">npx</span> sql-formatter-cli <span class="at">--file</span> <span class="va">$@</span> <span class="at">--out</span> <span class="va">$@</span><span class="kw">;</span></span>
<span id="cb3-22"><a aria-hidden="true" href="#cb3-22" tabindex="-1"></a>    <span class="cf">return</span> <span class="dv">0</span><span class="kw">;</span></span>
<span id="cb3-23"><a aria-hidden="true" href="#cb3-23" tabindex="-1"></a>  <span class="cf">fi</span></span>
<span id="cb3-24"><a aria-hidden="true" href="#cb3-24" tabindex="-1"></a></span>
<span id="cb3-25"><a aria-hidden="true" href="#cb3-25" tabindex="-1"></a>  <span class="ex">npx</span> prettier <span class="at">--write</span> <span class="at">--single-quote</span> <span class="va">$@</span><span class="kw">;</span></span>
<span id="cb3-26"><a aria-hidden="true" href="#cb3-26" tabindex="-1"></a></span>
<span id="cb3-27"><a aria-hidden="true" href="#cb3-27" tabindex="-1"></a>  <span class="bu">unset</span> <span class="va">FILENAME</span><span class="kw">;</span> <span class="bu">unset</span> <span class="va">EXTENSION</span><span class="kw">;</span></span>
<span id="cb3-28"><a aria-hidden="true" href="#cb3-28" tabindex="-1"></a><span class="kw">}</span></span></code></pre></div>
<p>Using the same code formatter, but added something to handle
<code>sql</code> files now!</p>
<h1 id="spell-check">Spell Check</h1>
<div class="sourceCode" id="cb4"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb4-1"><a aria-hidden="true" href="#cb4-1" tabindex="-1"></a><span class="co">## Spellcheck</span></span>
<span id="cb4-2"><a aria-hidden="true" href="#cb4-2" tabindex="-1"></a><span class="kw">function</span><span class="fu"> spellcheck-file()</span> <span class="kw">{</span></span>
<span id="cb4-3"><a aria-hidden="true" href="#cb4-3" tabindex="-1"></a>  <span class="ex">npx</span> spellchecker-cli <span class="at">--files</span> <span class="va">$@</span><span class="kw">;</span></span>
<span id="cb4-4"><a aria-hidden="true" href="#cb4-4" tabindex="-1"></a><span class="kw">}</span></span></code></pre></div>
<p>Handy spellchecker utility through <code>npx</code> is always
useful.</p>
<h1 id="what-the-commit">What the Commit</h1>
<div class="sourceCode" id="cb5"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb5-1"><a aria-hidden="true" href="#cb5-1" tabindex="-1"></a><span class="co">## What the Commit</span></span>
<span id="cb5-2"><a aria-hidden="true" href="#cb5-2" tabindex="-1"></a><span class="kw">function</span><span class="fu"> wtf()</span> <span class="kw">{</span> <span class="fu">git</span> commit <span class="at">-am</span> <span class="st">"</span><span class="va">$(</span><span class="ex">curl</span> http://whatthecommit.com/index.txt<span class="va">)</span><span class="st">"</span><span class="kw">;</span> <span class="kw">}</span></span></code></pre></div>
<p>For when I don’t know what to type in for a commit message.</p>
<h1 id="cheat-sheet">Cheat Sheet</h1>
<div class="sourceCode" id="cb6"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb6-1"><a aria-hidden="true" href="#cb6-1" tabindex="-1"></a><span class="ex">Cheat</span> Sheet</span>
<span id="cb6-2"><a aria-hidden="true" href="#cb6-2" tabindex="-1"></a><span class="co">## Cheat</span></span>
<span id="cb6-3"><a aria-hidden="true" href="#cb6-3" tabindex="-1"></a><span class="kw">function</span><span class="fu"> cheat()</span><span class="kw">{</span> <span class="ex">curl</span> https://cheat.sh/<span class="st">"</span><span class="va">$@</span><span class="st">"</span><span class="kw">;</span> <span class="kw">}</span></span></code></pre></div>
<p>When I don’t remember how to use a certain utility, this <a href="https://cheat.sh/">cheatsheet API</a> is always handy.</p>
<h1 id="git">Git</h1>
<div class="sourceCode" id="cb7"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb7-1"><a aria-hidden="true" href="#cb7-1" tabindex="-1"></a><span class="co">## Commit Count</span></span>
<span id="cb7-2"><a aria-hidden="true" href="#cb7-2" tabindex="-1"></a><span class="kw">function</span><span class="fu"> commit-count()</span> <span class="kw">{</span></span>
<span id="cb7-3"><a aria-hidden="true" href="#cb7-3" tabindex="-1"></a>  <span class="cf">if</span> <span class="bu">[</span> <span class="ot">-n</span> <span class="st">"</span><span class="va">$1</span><span class="st">"</span> <span class="bu">]</span></span>
<span id="cb7-4"><a aria-hidden="true" href="#cb7-4" tabindex="-1"></a>  <span class="cf">then</span></span>
<span id="cb7-5"><a aria-hidden="true" href="#cb7-5" tabindex="-1"></a>    <span class="fu">git</span> rev-list <span class="at">--count</span> <span class="va">$1</span><span class="kw">;</span></span>
<span id="cb7-6"><a aria-hidden="true" href="#cb7-6" tabindex="-1"></a>  <span class="cf">else</span></span>
<span id="cb7-7"><a aria-hidden="true" href="#cb7-7" tabindex="-1"></a>    <span class="bu">echo</span> <span class="st">"commit-count &lt;branch-name&gt;"</span><span class="kw">;</span></span>
<span id="cb7-8"><a aria-hidden="true" href="#cb7-8" tabindex="-1"></a>  <span class="cf">fi</span></span>
<span id="cb7-9"><a aria-hidden="true" href="#cb7-9" tabindex="-1"></a><span class="kw">}</span></span>
<span id="cb7-10"><a aria-hidden="true" href="#cb7-10" tabindex="-1"></a></span>
<span id="cb7-11"><a aria-hidden="true" href="#cb7-11" tabindex="-1"></a><span class="ex">Not</span> as useful, but interesting. I use this to get the number of commits on my current working branch.</span></code></pre></div>
<h1 id="document-generation">Document Generation</h1>
<div class="sourceCode" id="cb8"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb8-1"><a aria-hidden="true" href="#cb8-1" tabindex="-1"></a><span class="co">## Generate Markdown</span></span>
<span id="cb8-2"><a aria-hidden="true" href="#cb8-2" tabindex="-1"></a><span class="kw">function</span><span class="fu"> generate-doc()</span> <span class="kw">{</span> </span>
<span id="cb8-3"><a aria-hidden="true" href="#cb8-3" tabindex="-1"></a>  <span class="fu">cp</span> <span class="at">-rf</span> . /tmp<span class="kw">;</span></span>
<span id="cb8-4"><a aria-hidden="true" href="#cb8-4" tabindex="-1"></a>  <span class="cf">if</span> <span class="bu">[</span> <span class="ot">-n</span> <span class="st">"</span><span class="va">$2</span><span class="st">"</span> <span class="bu">]</span></span>
<span id="cb8-5"><a aria-hidden="true" href="#cb8-5" tabindex="-1"></a>  <span class="cf">then</span></span>
<span id="cb8-6"><a aria-hidden="true" href="#cb8-6" tabindex="-1"></a>    <span class="ex">pandoc</span> <span class="at">-s</span> <span class="va">$1</span> <span class="at">-c</span> <span class="va">$2</span> <span class="at">-o</span> <span class="st">"/tmp/</span><span class="va">$1</span><span class="st">.html"</span><span class="kw">;</span></span>
<span id="cb8-7"><a aria-hidden="true" href="#cb8-7" tabindex="-1"></a>  <span class="cf">else</span></span>
<span id="cb8-8"><a aria-hidden="true" href="#cb8-8" tabindex="-1"></a>    <span class="ex">pandoc</span> <span class="at">-s</span> <span class="va">$1</span> <span class="at">-o</span> <span class="st">"/tmp/</span><span class="va">$1</span><span class="st">.html"</span><span class="kw">;</span> </span>
<span id="cb8-9"><a aria-hidden="true" href="#cb8-9" tabindex="-1"></a>  <span class="cf">fi</span></span>
<span id="cb8-10"><a aria-hidden="true" href="#cb8-10" tabindex="-1"></a>  <span class="ex">open</span> <span class="st">"/tmp/</span><span class="va">$1</span><span class="st">.html"</span><span class="kw">;</span></span>
<span id="cb8-11"><a aria-hidden="true" href="#cb8-11" tabindex="-1"></a><span class="kw">}</span></span>
<span id="cb8-12"><a aria-hidden="true" href="#cb8-12" tabindex="-1"></a></span>
<span id="cb8-13"><a aria-hidden="true" href="#cb8-13" tabindex="-1"></a><span class="co">## Generate Slide</span></span>
<span id="cb8-14"><a aria-hidden="true" href="#cb8-14" tabindex="-1"></a><span class="kw">function</span><span class="fu"> generate-slide()</span> <span class="kw">{</span></span>
<span id="cb8-15"><a aria-hidden="true" href="#cb8-15" tabindex="-1"></a>  <span class="co"># https://revealjs.com/config/</span></span>
<span id="cb8-16"><a aria-hidden="true" href="#cb8-16" tabindex="-1"></a>  <span class="ex">pandoc</span> <span class="at">-t</span> revealjs <span class="dt">\</span></span>
<span id="cb8-17"><a aria-hidden="true" href="#cb8-17" tabindex="-1"></a>    <span class="at">-V</span> controls=<span class="st">"false"</span> <span class="dt">\</span></span>
<span id="cb8-18"><a aria-hidden="true" href="#cb8-18" tabindex="-1"></a>    <span class="at">-V</span> progress=<span class="st">"false"</span> <span class="dt">\</span></span>
<span id="cb8-19"><a aria-hidden="true" href="#cb8-19" tabindex="-1"></a>    <span class="at">-V</span> navigationMode=<span class="st">"linear"</span> <span class="dt">\</span></span>
<span id="cb8-20"><a aria-hidden="true" href="#cb8-20" tabindex="-1"></a>    <span class="at">-V</span> transition=<span class="st">"none"</span> <span class="dt">\</span></span>
<span id="cb8-21"><a aria-hidden="true" href="#cb8-21" tabindex="-1"></a>    <span class="at">-s</span> <span class="va">$1</span> <span class="at">-o</span> <span class="st">"/tmp/</span><span class="va">$1</span><span class="st">.html"</span><span class="kw">;</span></span>
<span id="cb8-22"><a aria-hidden="true" href="#cb8-22" tabindex="-1"></a>  <span class="fu">cp</span> <span class="at">-rf</span> . /tmp<span class="kw">;</span></span>
<span id="cb8-23"><a aria-hidden="true" href="#cb8-23" tabindex="-1"></a>  <span class="ex">open</span> <span class="st">"/tmp/</span><span class="va">$1</span><span class="st">.html"</span><span class="kw">;</span></span>
<span id="cb8-24"><a aria-hidden="true" href="#cb8-24" tabindex="-1"></a><span class="kw">}</span></span>
<span id="cb8-25"><a aria-hidden="true" href="#cb8-25" tabindex="-1"></a></span>
<span id="cb8-26"><a aria-hidden="true" href="#cb8-26" tabindex="-1"></a><span class="kw">function</span><span class="fu"> mmdc()</span> <span class="kw">{</span></span>
<span id="cb8-27"><a aria-hidden="true" href="#cb8-27" tabindex="-1"></a>  <span class="ex">npx</span> @mermaid-js/mermaid-cli <span class="va">$@</span><span class="kw">;</span></span>
<span id="cb8-28"><a aria-hidden="true" href="#cb8-28" tabindex="-1"></a><span class="kw">}</span></span></code></pre></div>
<p>Continuing to leverage <a href="https://pandoc.org/">pandoc</a>
whenever I need to create some quick slides or a document.</p>
<h1 id="homebrew-install">Homebrew Install</h1>
<div class="sourceCode" id="cb9"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb9-1"><a aria-hidden="true" href="#cb9-1" tabindex="-1"></a><span class="co">## Homebrew Install Script</span></span>
<span id="cb9-2"><a aria-hidden="true" href="#cb9-2" tabindex="-1"></a><span class="kw">function</span><span class="fu"> install-homebrew()</span> <span class="kw">{</span> <span class="ex">/bin/bash</span> <span class="at">-c</span> <span class="st">"</span><span class="va">$(</span><span class="ex">curl</span> <span class="at">-fsSL</span> https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh<span class="va">)</span><span class="st">"</span><span class="kw">;</span> <span class="kw">}</span></span></code></pre></div>
<p>Handy script to install <a href="https://brew.sh">homebrew</a> on a
new machine.</p>
<h1 id="vundle-install">Vundle Install</h1>
<div class="sourceCode" id="cb10"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb10-1"><a aria-hidden="true" href="#cb10-1" tabindex="-1"></a><span class="co">## Vundle Install Script</span></span>
<span id="cb10-2"><a aria-hidden="true" href="#cb10-2" tabindex="-1"></a><span class="kw">function</span><span class="fu"> install-vundle()</span> <span class="kw">{</span></span>
<span id="cb10-3"><a aria-hidden="true" href="#cb10-3" tabindex="-1"></a>  <span class="fu">git</span> clone https://github.com/VundleVim/Vundle.vim.git ~/.vim/bundle/Vundle.vim<span class="kw">;</span></span>
<span id="cb10-4"><a aria-hidden="true" href="#cb10-4" tabindex="-1"></a>  <span class="fu">sudo</span> gem install vundle-cli<span class="kw">;</span></span>
<span id="cb10-5"><a aria-hidden="true" href="#cb10-5" tabindex="-1"></a>  <span class="cf">if</span> <span class="ot">! </span><span class="fu">grep</span> <span class="at">-Fxq</span> <span class="st">'set rtp+=~/.vim/bundle/Vundle.vim'</span> ~/.vimrc</span>
<span id="cb10-6"><a aria-hidden="true" href="#cb10-6" tabindex="-1"></a>  <span class="cf">then</span></span>
<span id="cb10-7"><a aria-hidden="true" href="#cb10-7" tabindex="-1"></a>    <span class="bu">echo</span> <span class="st">"\nset nocompatible\nfiletype off\nset rtp+=~/.vim/bundle/Vundle.vim\ncall vundle#begin()\n\nPlugin 'VundleVim/Vundle.vim'\n\ncall vundle#end()\nfiletype plugin indent on"</span> <span class="op">&gt;&gt;</span> ~/.vimrc<span class="kw">;</span></span>
<span id="cb10-8"><a aria-hidden="true" href="#cb10-8" tabindex="-1"></a>  <span class="cf">fi</span></span>
<span id="cb10-9"><a aria-hidden="true" href="#cb10-9" tabindex="-1"></a><span class="kw">}</span></span></code></pre></div>
<p>I use <a href="https://github.com/VundleVim/Vundle.vim">vundle</a> to
manage my vim packages. But this <a href="https://github.com/baopham/vundle-cli">handy cli</a> makes
installing/removing those packages much easier.</p>
<h1 id="docker">Docker</h1>
<div class="sourceCode" id="cb11"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb11-1"><a aria-hidden="true" href="#cb11-1" tabindex="-1"></a><span class="kw">function</span><span class="fu"> clear-docker()</span> <span class="kw">{</span></span>
<span id="cb11-2"><a aria-hidden="true" href="#cb11-2" tabindex="-1"></a>  <span class="ex">docker</span> system prune <span class="at">-a</span> <span class="at">-f</span> <span class="at">--volumes</span></span>
<span id="cb11-3"><a aria-hidden="true" href="#cb11-3" tabindex="-1"></a><span class="kw">}</span></span>
<span id="cb11-4"><a aria-hidden="true" href="#cb11-4" tabindex="-1"></a></span>
<span id="cb11-5"><a aria-hidden="true" href="#cb11-5" tabindex="-1"></a><span class="kw">function</span><span class="fu"> kill-docker()</span> <span class="kw">{</span></span>
<span id="cb11-6"><a aria-hidden="true" href="#cb11-6" tabindex="-1"></a>  <span class="fu">killall</span> Docker <span class="kw">&amp;&amp;</span> <span class="ex">open</span> /Applications/Docker.app<span class="kw">;</span></span>
<span id="cb11-7"><a aria-hidden="true" href="#cb11-7" tabindex="-1"></a><span class="kw">}</span></span></code></pre></div>
<p>For when I just want to wipe docker of all images, containers, and
volumes.</p>
<p>Also something to help restart the docker daemon.</p>
<h1 id="google-cloud-platform">Google Cloud Platform</h1>
<div class="sourceCode" id="cb12"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb12-1"><a aria-hidden="true" href="#cb12-1" tabindex="-1"></a><span class="kw">function</span><span class="fu"> gcloud-adc()</span> <span class="kw">{</span></span>
<span id="cb12-2"><a aria-hidden="true" href="#cb12-2" tabindex="-1"></a>  <span class="ex">gcloud</span> auth login <span class="at">--update-adc</span></span>
<span id="cb12-3"><a aria-hidden="true" href="#cb12-3" tabindex="-1"></a><span class="kw">}</span></span>
<span id="cb12-4"><a aria-hidden="true" href="#cb12-4" tabindex="-1"></a></span>
<span id="cb12-5"><a aria-hidden="true" href="#cb12-5" tabindex="-1"></a><span class="kw">function</span><span class="fu"> use-gcloud-project ()</span> <span class="kw">{</span></span>
<span id="cb12-6"><a aria-hidden="true" href="#cb12-6" tabindex="-1"></a>  <span class="ex">gcloud</span> config set project <span class="st">"</span><span class="va">$1</span><span class="st">"</span><span class="kw">;</span></span>
<span id="cb12-7"><a aria-hidden="true" href="#cb12-7" tabindex="-1"></a><span class="kw">}</span></span>
<span id="cb12-8"><a aria-hidden="true" href="#cb12-8" tabindex="-1"></a></span>
<span id="cb12-9"><a aria-hidden="true" href="#cb12-9" tabindex="-1"></a><span class="kw">function</span><span class="fu"> impersonate()</span> <span class="kw">{</span></span>
<span id="cb12-10"><a aria-hidden="true" href="#cb12-10" tabindex="-1"></a>    <span class="cf">if</span> <span class="bu">[</span> <span class="ot">-z</span> <span class="st">"</span><span class="va">$1</span><span class="st">"</span> <span class="bu">]</span><span class="kw">;</span> <span class="cf">then</span></span>
<span id="cb12-11"><a aria-hidden="true" href="#cb12-11" tabindex="-1"></a>        <span class="bu">echo</span> <span class="st">"Must provide a service account to impersonate."</span></span>
<span id="cb12-12"><a aria-hidden="true" href="#cb12-12" tabindex="-1"></a>        <span class="cf">return</span> <span class="dv">1</span></span>
<span id="cb12-13"><a aria-hidden="true" href="#cb12-13" tabindex="-1"></a>    <span class="cf">fi</span></span>
<span id="cb12-14"><a aria-hidden="true" href="#cb12-14" tabindex="-1"></a>    <span class="ex">gcloud</span> config set auth/impersonate_service_account <span class="st">"</span><span class="va">$1</span><span class="st">"</span></span>
<span id="cb12-15"><a aria-hidden="true" href="#cb12-15" tabindex="-1"></a><span class="kw">}</span></span>
<span id="cb12-16"><a aria-hidden="true" href="#cb12-16" tabindex="-1"></a></span>
<span id="cb12-17"><a aria-hidden="true" href="#cb12-17" tabindex="-1"></a><span class="kw">function</span><span class="fu"> unimpersonate()</span> <span class="kw">{</span></span>
<span id="cb12-18"><a aria-hidden="true" href="#cb12-18" tabindex="-1"></a>    <span class="ex">gcloud</span> config unset auth/impersonate_service_account</span>
<span id="cb12-19"><a aria-hidden="true" href="#cb12-19" tabindex="-1"></a><span class="kw">}</span></span>
<span id="cb12-20"><a aria-hidden="true" href="#cb12-20" tabindex="-1"></a></span>
<span id="cb12-21"><a aria-hidden="true" href="#cb12-21" tabindex="-1"></a><span class="kw">function</span><span class="fu"> connect()</span> <span class="kw">{</span></span>
<span id="cb12-22"><a aria-hidden="true" href="#cb12-22" tabindex="-1"></a>    <span class="co"># Set the context</span></span>
<span id="cb12-23"><a aria-hidden="true" href="#cb12-23" tabindex="-1"></a>    <span class="va">CONTEXT</span><span class="op">=</span><span class="va">${1}</span></span>
<span id="cb12-24"><a aria-hidden="true" href="#cb12-24" tabindex="-1"></a></span>
<span id="cb12-25"><a aria-hidden="true" href="#cb12-25" tabindex="-1"></a>    <span class="bu">export</span> <span class="va">PGUSER</span><span class="op">=</span><span class="va">$(</span><span class="ex">vault</span> kv get <span class="at">-field</span><span class="op">=</span>username secrets/<span class="va">${CONTEXT}</span>/postgres-terraform<span class="va">)</span></span>
<span id="cb12-26"><a aria-hidden="true" href="#cb12-26" tabindex="-1"></a>    <span class="bu">export</span> <span class="va">PGPASSWORD</span><span class="op">=</span><span class="va">$(</span><span class="ex">vault</span> kv get <span class="at">-field</span><span class="op">=</span>password <span class="st">"secrets/</span><span class="va">${CONTEXT}</span><span class="st">/postgres-terraform"</span><span class="va">)</span></span>
<span id="cb12-27"><a aria-hidden="true" href="#cb12-27" tabindex="-1"></a>    <span class="ex">psql</span> <span class="at">-h</span> 127.0.0.1 <span class="at">-p</span> 54320  <span class="at">-d</span> <span class="st">"</span><span class="va">${CONTEXT}</span><span class="st">"</span></span>
<span id="cb12-28"><a aria-hidden="true" href="#cb12-28" tabindex="-1"></a><span class="kw">}</span></span></code></pre></div>
<ul>
<li>Logging into <a href="https://cloud.google.com/docs/authentication/provide-credentials-adc">Google
ADC</a></li>
<li>Setting GCP project to work with</li>
<li><a href="https://cloud.google.com/iam/docs/impersonating-service-accounts">Service
account impersonation</a></li>
<li>Connect to <a href="https://cloud.google.com/sql">cloud sql</a></li>
</ul>
<h1 id="vault">Vault</h1>
<div class="sourceCode" id="cb13"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb13-1"><a aria-hidden="true" href="#cb13-1" tabindex="-1"></a><span class="kw">function</span><span class="fu"> vs()</span> <span class="kw">{</span></span>
<span id="cb13-2"><a aria-hidden="true" href="#cb13-2" tabindex="-1"></a>    <span class="co"># Verify inputs</span></span>
<span id="cb13-3"><a aria-hidden="true" href="#cb13-3" tabindex="-1"></a>    <span class="va">environment</span><span class="op">=</span><span class="st">"</span><span class="va">$1</span><span class="st">"</span></span>
<span id="cb13-4"><a aria-hidden="true" href="#cb13-4" tabindex="-1"></a>    <span class="cf">if</span> <span class="bu">[</span> <span class="st">"</span><span class="va">$environment</span><span class="st">"</span> <span class="ot">!=</span> <span class="st">"common"</span> <span class="bu">]</span> <span class="kw">&amp;&amp;</span> <span class="bu">[</span> <span class="st">"</span><span class="va">$environment</span><span class="st">"</span> <span class="ot">!=</span> <span class="st">"staging"</span> <span class="bu">]</span> <span class="kw">&amp;&amp;</span> <span class="bu">[</span> <span class="st">"</span><span class="va">$environment</span><span class="st">"</span> <span class="ot">!=</span> <span class="st">"production"</span> <span class="bu">]</span><span class="kw">;</span> <span class="cf">then</span></span>
<span id="cb13-5"><a aria-hidden="true" href="#cb13-5" tabindex="-1"></a>        <span class="bu">echo</span> <span class="st">"</span><span class="dt">\"</span><span class="va">$environment</span><span class="dt">\"</span><span class="st"> is not a valid environment."</span></span>
<span id="cb13-6"><a aria-hidden="true" href="#cb13-6" tabindex="-1"></a>        <span class="cf">return</span> <span class="dv">1</span></span>
<span id="cb13-7"><a aria-hidden="true" href="#cb13-7" tabindex="-1"></a>    <span class="cf">fi</span></span>
<span id="cb13-8"><a aria-hidden="true" href="#cb13-8" tabindex="-1"></a></span>
<span id="cb13-9"><a aria-hidden="true" href="#cb13-9" tabindex="-1"></a>    <span class="co"># Setup</span></span>
<span id="cb13-10"><a aria-hidden="true" href="#cb13-10" tabindex="-1"></a>    <span class="bu">[</span> <span class="ot">!</span> <span class="ot">-d</span> ~/.vault-tokens <span class="bu">]</span> <span class="kw">&amp;&amp;</span> <span class="fu">mkdir</span> ~/.vault-tokens</span>
<span id="cb13-11"><a aria-hidden="true" href="#cb13-11" tabindex="-1"></a></span>
<span id="cb13-12"><a aria-hidden="true" href="#cb13-12" tabindex="-1"></a>    <span class="co"># Move the current environment's token to the correct location</span></span>
<span id="cb13-13"><a aria-hidden="true" href="#cb13-13" tabindex="-1"></a>    <span class="cf">if</span> <span class="bu">[</span> <span class="ot">-f</span> ~/.vault-tokens/current-environment <span class="bu">]</span> <span class="kw">&amp;&amp;</span> <span class="bu">[</span> <span class="ot">-f</span> ~/.vault-token <span class="bu">]</span><span class="kw">;</span> <span class="cf">then</span></span>
<span id="cb13-14"><a aria-hidden="true" href="#cb13-14" tabindex="-1"></a>        <span class="va">current</span><span class="op">=</span><span class="st">"</span><span class="va">$(</span><span class="fu">cat</span> ~/.vault-tokens/current-environment<span class="va">)</span><span class="st">"</span></span>
<span id="cb13-15"><a aria-hidden="true" href="#cb13-15" tabindex="-1"></a>        <span class="fu">cp</span> ~/.vault-token ~/.vault-tokens/<span class="va">${current}</span></span>
<span id="cb13-16"><a aria-hidden="true" href="#cb13-16" tabindex="-1"></a>    <span class="cf">fi</span></span>
<span id="cb13-17"><a aria-hidden="true" href="#cb13-17" tabindex="-1"></a></span>
<span id="cb13-18"><a aria-hidden="true" href="#cb13-18" tabindex="-1"></a>    <span class="co"># Set the new current environment</span></span>
<span id="cb13-19"><a aria-hidden="true" href="#cb13-19" tabindex="-1"></a>    <span class="bu">echo</span> <span class="st">"</span><span class="va">${environment}</span><span class="st">"</span> <span class="op">&gt;</span> ~/.vault-tokens/current-environment</span>
<span id="cb13-20"><a aria-hidden="true" href="#cb13-20" tabindex="-1"></a></span>
<span id="cb13-21"><a aria-hidden="true" href="#cb13-21" tabindex="-1"></a>    <span class="co"># Set the correct vault address</span></span>
<span id="cb13-22"><a aria-hidden="true" href="#cb13-22" tabindex="-1"></a>    <span class="cf">if</span> <span class="bu">[</span> <span class="st">"</span><span class="va">$environment</span><span class="st">"</span> <span class="ot">=</span> <span class="st">"common"</span> <span class="bu">]</span><span class="kw">;</span> <span class="cf">then</span></span>
<span id="cb13-23"><a aria-hidden="true" href="#cb13-23" tabindex="-1"></a>        <span class="bu">export</span> <span class="va">VAULT_ADDR</span><span class="op">=</span><span class="st">"https://vault.stallions.dev"</span></span>
<span id="cb13-24"><a aria-hidden="true" href="#cb13-24" tabindex="-1"></a>    <span class="cf">else</span></span>
<span id="cb13-25"><a aria-hidden="true" href="#cb13-25" tabindex="-1"></a>        <span class="bu">export</span> <span class="va">VAULT_ADDR</span><span class="op">=</span><span class="st">"https://vault.</span><span class="va">${environment}</span><span class="st">.stallions.dev"</span></span>
<span id="cb13-26"><a aria-hidden="true" href="#cb13-26" tabindex="-1"></a>    <span class="cf">fi</span></span>
<span id="cb13-27"><a aria-hidden="true" href="#cb13-27" tabindex="-1"></a></span>
<span id="cb13-28"><a aria-hidden="true" href="#cb13-28" tabindex="-1"></a>    <span class="co"># Get the token from the current environment if it exist</span></span>
<span id="cb13-29"><a aria-hidden="true" href="#cb13-29" tabindex="-1"></a>    <span class="cf">if</span> <span class="bu">[</span> <span class="ot">-f</span> ~/.vault-tokens/<span class="va">${environment}</span> <span class="bu">]</span> <span class="kw">;</span> <span class="cf">then</span></span>
<span id="cb13-30"><a aria-hidden="true" href="#cb13-30" tabindex="-1"></a>        <span class="fu">cp</span> ~/.vault-tokens/<span class="va">${environment}</span> ~/.vault-token</span>
<span id="cb13-31"><a aria-hidden="true" href="#cb13-31" tabindex="-1"></a>    <span class="cf">fi</span></span>
<span id="cb13-32"><a aria-hidden="true" href="#cb13-32" tabindex="-1"></a></span>
<span id="cb13-33"><a aria-hidden="true" href="#cb13-33" tabindex="-1"></a>    <span class="co"># Prompt login if the token is not valid</span></span>
<span id="cb13-34"><a aria-hidden="true" href="#cb13-34" tabindex="-1"></a>    <span class="cf">if</span> <span class="ot">! </span><span class="ex">vault</span> token lookup <span class="op">&gt;</span> /dev/null<span class="kw">;</span> <span class="cf">then</span></span>
<span id="cb13-35"><a aria-hidden="true" href="#cb13-35" tabindex="-1"></a>        <span class="ex">vault</span> login <span class="at">--method</span> oidc</span>
<span id="cb13-36"><a aria-hidden="true" href="#cb13-36" tabindex="-1"></a>    <span class="cf">fi</span></span>
<span id="cb13-37"><a aria-hidden="true" href="#cb13-37" tabindex="-1"></a><span class="kw">}</span></span></code></pre></div>
<p>This script helps me switch <a href="https://www.vaultproject.io/">vault</a> contexts for pulling
secrets in different environments like staging and production.</p>
<h1 id="generate-id">Generate ID</h1>
<div class="sourceCode" id="cb14"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb14-1"><a aria-hidden="true" href="#cb14-1" tabindex="-1"></a><span class="kw">function</span><span class="fu"> uuid()</span> <span class="kw">{</span></span>
<span id="cb14-2"><a aria-hidden="true" href="#cb14-2" tabindex="-1"></a>    <span class="fu">uuidgen</span> <span class="kw">|</span> <span class="fu">tr</span> <span class="st">'[:upper:]'</span> <span class="st">'[:lower:]'</span></span>
<span id="cb14-3"><a aria-hidden="true" href="#cb14-3" tabindex="-1"></a><span class="kw">}</span></span>
<span id="cb14-4"><a aria-hidden="true" href="#cb14-4" tabindex="-1"></a></span>
<span id="cb14-5"><a aria-hidden="true" href="#cb14-5" tabindex="-1"></a><span class="kw">function</span><span class="fu"> vin()</span> <span class="kw">{</span></span>
<span id="cb14-6"><a aria-hidden="true" href="#cb14-6" tabindex="-1"></a>  <span class="bu">echo</span> <span class="st">"</span><span class="va">$(</span><span class="ex">curl</span> <span class="at">-sS</span> https://randomvin.com/getvin.php\?type\=fake <span class="kw">|</span> <span class="fu">tr</span> <span class="at">-d</span> <span class="st">'[:space:]'</span><span class="va">)</span><span class="st">"</span></span>
<span id="cb14-7"><a aria-hidden="true" href="#cb14-7" tabindex="-1"></a><span class="kw">}</span></span></code></pre></div>
<p>I use this to create new uuids and new <a href="https://en.wikipedia.org/wiki/Vehicle_identification_number">vin
numbers</a>.</p>
    ]]></content:encoded>
</item>
<item>
<title>What’s in My RC 2021</title>
<link>https://unitehenry.com/blog/whats-in-my-rc-2021</link>
<description>A tour of my RC file in 2021.</description>
<pubDate>Sat, 29 May 2021 00:00:00 +0000</pubDate>
<content:encoded><![CDATA[
      <!-- prettier-ignore -->
<h1 id="whats-in-my-rc-2021">What’s in My RC 2021</h1>
<p>2021-05-29</p>
<p>Welcome to a tour of my <code>.zshrc</code> file! I thought it would
be fun to take a dive into the different tools I use to elevate my
developer game.</p>
<p><strong>If you want to see my whole config, I keep all my setup files
on GitHub:</strong> <a href="https://github.com/unitehenry/config">https://github.com/unitehenry/config</a></p>
<h2 id="tools-i-use">Tools I Use</h2>
<p>Before we dive into my configuration, here are the tools I use that
help me speed up my tasks as a developer:</p>
<p><code>fzf</code> | <a href="https://github.com/junegunn/fzf">Fuzzy
File Finder</a></p>
<p><code>pandoc</code> | <a href="https://pandoc.org">Pandoc Document
Converter</a></p>
<p><code>brew</code> | <a href="https://brew.sh">MacOS Package
Manager</a></p>
<h2 id="the-variables">The Variables</h2>
<pre><code># EDITOR
export EDITOR="vi";
export VISUAL="vi";

# iCloud Directory
export DOCS="/Users/henryunite/Library/Mobile Documents/com~apple~CloudDocs";

# Work Directory
export WORK="/Users/henryunite/Projects/bicycletransit";</code></pre>
<p>Pretty straightforward, but these are what I use to:</p>
<ul>
<li>Default editing to be opened in vim</li>
<li>Reference my iCloud directory which I use to keep all my personal
files</li>
<li>A quick reference to where I keep all my work repositories, notes,
projects</li>
</ul>
<h2 id="credentials">Credentials</h2>
<pre><code># Credentials Fetcher
function username() {
  export PASS_BACK_PATH=$(pwd);
  cd $DOCS/passwords;
  echo $(decrypt-file $(fzf) | grep "Username:" | cut -d ":" -f2) | pbcopy;
  cd $PASS_BACK_PATH &amp;&amp; unset PASS_BACK_PATH;
}

function password() {
  export PASS_BACK_PATH=$(pwd);
  cd $DOCS/passwords;
  echo $(decrypt-file $(fzf) | grep "Password:" | cut -d ":" -f2) | pbcopy;
  cd $PASS_BACK_PATH &amp;&amp; unset PASS_BACK_PATH;
}</code></pre>
<p>There are so many chrome extensions, keychains, any ways to access
your passwords. I personally encrypt my passwords in my cloud storage so
I can access them by utilizing a <code>aes-256-cbc</code> decryption
tool.</p>
<h2 id="file-formatting">File Formatting</h2>
<pre><code>## Code Formatter
function format-file() {
  export FILENAME="$(basename $@)";
  export EXTENSION="${FILENAME##*.}";

  if [ $EXTENSION = 'py' ]
  then
    yapf --in-place $@;
    return 0;
  fi

  if [ $EXTENSION = 'php' ]
  then
    php-cs-fixer fix $@;
    rm .php_cs.cache;
    return 0;
  fi

  npx prettier --write --single-quote $@;

  unset FILENAME; unset EXTENSION;
}</code></pre>
<p>File formatter that handles the languages I use on a day-to-day
basis. It gets the job done for most file types including JSON, YAML,
and even markdown.</p>
<h2 id="spell-check">Spell Check</h2>
<pre><code>## Spellcheck
function spellcheck-file() {
  npx spellchecker-cli --files $@;
}</code></pre>
<p>When you’re writing as much markdown documentation as me, you’ll want
an easy way to spell check your files.</p>
<h2 id="what-the-commit">What the Commit</h2>
<pre><code>## What the Commit
function wtf() { git commit -am "$(curl http://whatthecommit.com/index.txt)"; }</code></pre>
<p>This is a gimmick, but if you ever just want to commit file changes
and you just don’t know what to say in the commit message, <a href="http://whatthecommit.com/">what the commit</a> is just a fun
resource to get whacky commit messages.</p>
<h2 id="cheat-sheet">Cheat Sheet</h2>
<pre><code>## Cheat
function cheat(){ curl https://cheat.sh/"$@"; }</code></pre>
<p>There are so many times I use a CLI tool and can’t remember simple
commands and options that it takes to perform certain tasks. Check out
<a href="https://cheat.sh">cheat.sh</a> if you’re looking for an easy
way to reference different CLI tools.</p>
<h2 id="document-generation">Document Generation</h2>
<pre><code>## Generate Markdown
function generate-doc() { 
  cp -rf . /tmp;
  if [ -n "$2" ]
  then
    pandoc -s $1 -c $2 -o "/tmp/$1.html";
  else
    pandoc -s $1 -o "/tmp/$1.html"; 
  fi
  open "/tmp/$1.html";
}

## Generate Slide
function generate-slide() {
  # https://revealjs.com/config/
  pandoc -t revealjs \
    -V progress="false" \
    -V navigationMode="linear" \
    -V transition="none" \
    -s $1 -o "/tmp/$1.html";
  cp -rf . /tmp;
  open "/tmp/$1.html";
}</code></pre>
<p>I use markdown to write documentation all the time, but if I need to
send a coworker a document or present a slide with content that is
written in markdown, I’ll use <a href="https://pandoc.org">pandoc</a> to
generate these intermediary file formats.</p>
<p>It’s really nice to leverage CSS when I want to make my documents
look nice or need a clean way to look at markdown files.</p>
<h2 id="file-encryption">File Encryption</h2>
<pre><code>## Encrypt : aes-256-cbc
function encrypt-file() {
  if [ -z $@ ]
  then
    echo -n "Enter Encrypt Phrase: "; read -s ENCRYPTINPUT; echo "\n";
    echo $ENCRYPTINPUT | openssl enc -aes-256-cbc;
    unset ENCRYPTINPUT;
  else
    openssl enc -aes-256-cbc -in $@;
  fi
}

## Decrypt : aes-256-cbc
function decrypt-file() {
  if [ -z $@ ]
  then
    openssl enc -d -aes-256-cbc;
  else
    openssl enc -aes-256-cbc -d -in $@;
  fi
}</code></pre>
<p>It’s nice to have a quick way to encrypt and decrypt files with
sensitive information.</p>
<h2 id="homebrew">Homebrew</h2>
<pre><code>## Homebrew Install Script
function install-homebrew() { /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"; }</code></pre>
<p>This is just in my configuration in the event that I just want to
install <a href="https://brew.sh">homebrew</a> without copying and
pasting the install script from the website.</p>
<p>If I have a new Mac I need to setup, it’ll make the setup so much
easier.</p>
<h2 id="version-managers">Version Managers</h2>
<pre><code>source ~/.nvmrc;
source ~/.rvmrc;</code></pre>
<p>I’ve been using <code>nvm</code> and <code>rvm</code> to manage my
node and ruby installations. They append rc scripts to load into your
base rc file which I extract into their own designated files and load
them in at the end.</p>
    ]]></content:encoded>
</item>
<item>
<title>Building My Portfolio
Website With Nextjs</title>
<link>https://unitehenry.com/blog/building-my-portfolio-website-with-nextjs</link>
<description>Building a portfolio website with Nextjs.</description>
<pubDate>Fri, 20 Nov 2020 00:00:00 +0000</pubDate>
<content:encoded><![CDATA[
      <!-- prettier-ignore -->
<h1 id="building-my-portfolio-website-with-nextjs">Building My Portfolio
Website With Nextjs</h1>
<p>2020-11-20</p>
<p>It’s time for a new portfolio website! This time I decided to use
Next.js to generate my static site with these principles in mind:</p>
<ol type="1">
<li>Take a <a href="https://github.com/unitehenry/unitehenry/blob/master/README.md">README</a>
markdown file of my resume and convert it to a static homepage</li>
<li>Use <a href="https://www.w3schools.com/html/html5_semantic_elements.asp">semantic
HTML</a> with global styles for easy customization</li>
<li>Adding <a href="https://nextjs.org/docs/basic-features/pages">next
pages</a> will append links to the homepage</li>
</ol>
<h2 id="readme-conversion">README Conversion</h2>
<p>The core concepts of this project are built on the foundation of
these methods:</p>
<ol type="1">
<li>Bootstrap a <a href="https://nextjs.org/docs/api-reference/create-next-app">create-next-app</a></li>
<li>Use the <a href="https://nextjs.org/docs/basic-features/data-fetching">getStaticProps</a>
to generate HTML from the README with <a href="https://github.com/showdownjs/showdown">showdown</a></li>
<li>Use <a href="https://reactjs.org/docs/dom-elements.html">dangerouslySetInnerHTML</a>
for SEO optimization</li>
</ol>
<h3 id="getting-started-with-next">Getting Started with Next</h3>
<p>We can start bootstrapping our application using the <a href="https://nextjs.org/docs/api-reference/create-next-app">create-next-app</a>
npm script.</p>
<pre><code>$ npx create-next-app</code></pre>
<h3 id="generating-html-from-readme">Generating HTML from README</h3>
<p>Using <a href="https://nextjs.org/docs/basic-features/data-fetching">getStaticProps</a>
and <a href="https://github.com/showdownjs/showdown">showdown</a>, we
can generate some HTML to use for our site generation.</p>
<pre><code>export async function getStaticProps() {
  const path = require('path');
  const fs = require('fs');
  const { Converter } = require('showdown');
  const converter = new Converter();

  function parseREADME() {
    return new Promise((res) =&gt; {
      fs.readFile(path.join(process.cwd(), 'README.md'), (err, data) =&gt; {
        const readme = data.toString();
        const html = converter.makeHtml(pReadme);
        res(html);
      });
    });
  }

  const html = await parseREADME();

  return {
    props: { html },
  };
}</code></pre>
<h3 id="serving-html-optimized-for-seo">Serving HTML optimized for
SEO</h3>
<p>The key to using <a href="https://reactjs.org/docs/dom-elements.html">dangerouslySetInnerHTML</a>
with next.js is that we want to ensure the content of our HTML is served
as static content for SEO.</p>
<pre><code>return (
    &lt;div&gt;
      &lt;Head&gt;
        &lt;title&gt; {title} &lt;/title&gt;
        &lt;link rel="icon" href="/favicon.ico" /&gt;
      &lt;/Head&gt;

      &lt;main dangerouslySetInnerHTML={{ __html: html }} /&gt;

      &lt;footer&gt; 

      &lt;/footer&gt;
    &lt;/div&gt;
  );</code></pre>
<h2 id="semantic-styling">Semantic Styling</h2>
<p>After your content is being injected in the page, you should be
staring at a wall of black and white text like this:</p>
<figure>
<img alt="“Unstyled README Homepage”" src="/images/b568ca02-3d59-4bf6-8633-8f88d1823de5.png">
<figcaption aria-hidden="true">“Unstyled README Homepage”</figcaption>
</figure>
<p>Using the <a href="https://nextjs.org/docs/basic-features/built-in-css-support">global.css</a>
file provided by next, we can globally style semantic elements like
this:</p>
<pre><code>body {
     /* CSS Styles */
}

main {
     /* CSS Styles */
}

main hr {
     /* CSS Styles */
}

main strong {
     /* CSS Styles */
}

main p {
     /* CSS Styles */
}

main h1, main h2, main h3, main h4, main h5, main h6 {
     /* CSS Styles */
}

main ul, main ol {
     /* CSS Styles */
}

main li {
     /* CSS Styles */
}

main a {
     /* CSS Styles */
}</code></pre>
<h2 id="page-extensibility">Page Extensibility</h2>
<p>One of the design concepts I wanted to implement was the idea that
you could add a next page in the pages directory and a navigation link
be appended to the homepage.</p>
<p>Taking advantage of the the <a href="https://nextjs.org/docs/basic-features/data-fetching">getStaticProps</a>
function, we can use node to read the directory, exclude unrelated
files, and generate links in our homepage.</p>
<pre><code>// CONFIG['pageExcludes'] = [ 'app', 'api', 'index']

  function getPages() {
    return new Promise((res) =&gt; {
      fs.readdir(path.join(process.cwd(), 'pages'), (err, data) =&gt; {
        const pageFiles = data.filter((f) =&gt; {
          return !CONFIG['pageExcludes'].filter((ex) =&gt; f.includes(ex)).pop();
        });

        res(pageFiles.map((p) =&gt; p.replace('.js', '')));
      });
    });
  }

  const html = await parseREADME();
  const pages = await getPages();

  return {
    props: { html, pages },
  };</code></pre>
<pre><code>      &lt;footer&gt; 
        &lt;div id="pages"&gt;
          { pages.map((p) =&gt; p ? &lt;a key={p} href={`/${p}`}&gt;{ p }&lt;/a&gt; : null }
        &lt;/div&gt;
      &lt;/footer&gt;</code></pre>
<h3 id="gathering-my-blog-post-data">Gathering my Blog Post Data</h3>
<p>With this feature, I can now create unique CMS pages to extend my
static site. Let’s create a blog page to fetch my DEV posts.</p>
<p>I’ll be using the <a href="https://github.com/axios/axios">axios</a>
library to make a request to the DEV api, gather my posts data, and send
those props to the page for static site generation. Again, taking
advantage of the <a href="https://nextjs.org/docs/basic-features/data-fetching">getStaticProps</a>
hook.</p>
<pre><code>// pages/blog.js

export async function getStaticProps() {
  const axios = require('axios');

  function getArticle() {
    return new Promise(async (res) =&gt; {
      const req = await axios({
        method: 'GET',
        url: 'https://dev.to/api/articles?username=unitehenry'
      });

      if(req['data']) {
        try {
          const data = req['data'];
          res(data.map((article) =&gt; {
            return {
              title: article['title'], 
              description: article['description'], 
              url: article['url'],
              date: article['created_at'],
              image: article['cover_image']
            };
          })); 
        } catch(e) {
          res([]);
        }
      } else {
        res([]);
      }
    }); 

  }

  const articles = await getArticle();

  return {
    props: { articles }
  }
}</code></pre>
<pre><code>        &lt;section&gt;

        { (articles.length === 0) &amp;&amp; &lt;p&gt;No Blog Posts&lt;/p&gt;}

        {
          articles.map(({ title, description, date, url, image }) =&gt; {
            return (
              &lt;article key={title} className={style['blog-article']}&gt;
                { image ? &lt;img src={image} /&gt; : null}
                &lt;div className={style['blog-article-content']}&gt;
                  &lt;h2&gt;{ title }&lt;/h2&gt;
                  &lt;p&gt;{ description }&lt;/p&gt;
                  &lt;a title="Read Article" className={style['blog-button']} href={url} target="_blank"&gt;Read Article&lt;/a&gt;
                &lt;/div&gt;
              &lt;/article&gt;
            );
          })
        }

        &lt;/section&gt;</code></pre>
<figure>
<img alt="“Portfolio Footer Pages”" src="/images/7f6ed5e9-a9b8-45bb-b91a-43686d661b42.png">
<figcaption aria-hidden="true">“Portfolio Footer Pages”</figcaption>
</figure>
<h2 id="bootstrapping-of-my-repository">Bootstrapping of my
Repository</h2>
<p>If you want to see the source code or fork this repo and generate
your own static site, I’ve created a <a href="https://github.com/unitehenry/unitehenry">GitHub repository</a>
and <a href="https://github.com/unitehenry/unitehenry/wiki">documented
in detail</a> how to customize the code for your own static portfolio
site.</p>
<h3 id="github-trick">GitHub Trick</h3>
<p>As a side note, there is a <a href="https://docs.github.com/en/free-pro-team@latest/github/setting-up-and-managing-your-github-profile/managing-your-profile-readme">GitHub
trick</a> that will take your README and display it on your GitHub
profile as well.</p>
<figure>
<img alt="“GitHub Profile”" src="/images/3f889b6e-e224-44d1-b895-f3cf2462800c.png">
<figcaption aria-hidden="true">“GitHub Profile”</figcaption>
</figure>
    ]]></content:encoded>
</item>
<item>
<title>Striven Editor</title>
<link>https://unitehenry.com/blog/striven-editor</link>
<description>At Striven, I led the research and development of a lightweight,
in-house WYSIWYG editor to replace the heavy Kendo UI component.</description>
<pubDate>Sun, 27 Oct 2019 00:00:00 +0000</pubDate>
<content:encoded><![CDATA[
      <!-- prettier-ignore -->
<h1 id="striven-editor">Striven Editor</h1>
<p>2019-10-27</p>
<h2 id="introduction">Introduction</h2>
<p>At Striven, we were looking for potential editors that would be
suitable for our client side customer portal revamp. In the past, we
have always used the <a href="https://www.telerik.com/kendo-ui">Kendo
UI</a> editor. We were dynamically loading these components into our
pages, but even then the editor was shipping over 1MB of scripts over
the network.</p>
<p>One of the key features of this portal was its lightweight
optimization. When you look at the Kendo UI minified script over the
network, you’ll notice a whoping 1.3MB are being shipped to the browser.
With the editor component alone making up nearly 1MB of that script.</p>
<figure>
<img alt="“Kendo UI Script Size”" src="/images/0bcda974-b314-4aaf-b3ac-cc59534b5748.png">
<figcaption aria-hidden="true">“Kendo UI Script Size”</figcaption>
</figure>
<p>An editor was a fundamental component of this customer portal, so we
wanted to provide a more optimal solution to our users. I was tasked
with the research of finding a lighter editor with just as much, or as
much as we needed, functionality.</p>
<p>These were some notable candidates:</p>
<ul>
<li><a href="https://quilljs.com/">Quill</a></li>
<li><a href="https://froala.com/wysiwyg-editor/">Froala</a></li>
<li><a href="https://www.tiny.cloud/features">TineMCE</a></li>
</ul>
<h2 id="quill">Quill</h2>
<p>Quill would be great; its open source, inline, and used by top
companies. My experience when trying to integrate with the editor
involved the developer push for the adoption of the editor’s <a href="https://quilljs.com/docs/delta/">delta</a> api.</p>
<p>My advice to anyone trying to include this control in their project
is that you’ll have a much easier time designing your system with the
concept of delta in mind than trying to bring the concept of the quill
editor and its delta api into an existing system.</p>
<h2 id="froala-and-tinymce">Froala and TinyMCE</h2>
<p>So these editors are obviously top tier editors, but usually have
some licensing and enterprise support costs that we weren’t ready to
explore just yet. I never tried to demo or pitch it to my team, but I
still think these were candidates worth considering when all else
failed.</p>
<h2 id="undertaking-a-custom-built-editor">Undertaking a Custom Built
Editor</h2>
<p>After working on implementing the ability to mention users in posts
and exploring all these different editor solutions, I formed an
understanding of the underlying concepts that went into the inner
workings of a WYSIWYG editor. My manager was convinced that I was
capable of taking on the project of building a tailored editor for
Striven, as it would be worth the time and investment to work on this
component in house.</p>
<p>We decided to work on the component in phases.</p>
<p>What did we need out of the initial phase of the control?</p>
<ul>
<li>Simple Editing Functions (bold, italic, underline, unordered
lists)</li>
<li>File Attachments and Link Insertions</li>
<li>Mention Support</li>
</ul>
<p>With these functionalities in pipeline, editor phase one development
was underway. But I decided that I wasn’t going to do it alone.</p>
<h2 id="the-benefits-of-open-source">The Benefits of Open Source</h2>
<p>With permission from my manager, I decided that open sourcing this
control would have the following benefits:</p>
<ul>
<li>Faster development</li>
<li>Development guidance from the community on building an editor</li>
<li>Product brand awareness</li>
<li>Component development independent from the internal system</li>
</ul>
<p>The idea was a success*, as I could gain input from the open source
community and work on the component without having to rely on the
internal structure of our client side ecosystem. I also learned a lot as
a project maintainer and was really proud of the overall traction that
the editor had received in early development.</p>
<figure>
<img alt="“editor github page”" src="/images/963fcb0b-63ed-44bc-8fcb-a79882ee237d.png">
<figcaption aria-hidden="true">“editor github page”</figcaption>
</figure>
<p>*Even though 13 contributors isn’t a terribly significant amount, I
still take pride in it 😅</p>
<h2 id="what-i-learned-from-this-project">What I Learned From This
Project</h2>
<figure>
<img alt="“editor open source demo”" src="/images/d03e30dd-b964-4199-b2b6-f56168b44b06.png">
<figcaption aria-hidden="true">“editor open source demo”</figcaption>
</figure>
<p>I’ve been actively maintaining this <a href="https://github.com/striven-erp/striven-editor">code base</a> for
about a year now and if I could given some wisdom to the young software
engineer that does it next, this would be the advice I would give:</p>
<p>Avoid <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand">document.execCommand</a></p>
<ul>
<li>It’s old, buggy, and just a pain of an API to use</li>
<li>It’s one of those Internet Explorer that still lives to see modern
web development</li>
</ul>
<p>Start with an engine or library</p>
<ul>
<li>I’ve explored ways I would’ve redesigned this component on the <a href="https://github.com/basecamp/trix">trix</a> engine</li>
<li>I’ve considered how nice it would’ve been to use <a href="https://getcontenttools.com/api/content-edit"><code>contenteditable</code></a>
libraries</li>
</ul>
<p>Understand the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Range">Range</a>
API</p>
<ul>
<li>Although I don’t see this system going anywhere in future iterations
of the browser, I still believe it to be mediocre</li>
<li>Learning how to use the Range API and <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/getSelection"><code>window.getSelection()</code></a>
will make your life much easier</li>
<li>Try exploring libraries like <a href="https://github.com/timdown/rangy">rangy</a></li>
</ul>
<h2 id="final-thoughts">Final Thoughts</h2>
<p>When I reflect on the development of this project, my goal was always
to accomplish two things:</p>
<ul>
<li>Allow developers to contribute, collaborate, and learn open source
with a smaller scale project</li>
<li>It’s an editor for Striven, not for anything else (but feel free to
use it)</li>
</ul>
<p>It’s satisfying to have the ability of opening an issue and letting
someone contribute to this <a href="https://github.com/striven-erp/striven-editor">project</a>. I also
enjoy the ability to work on this component in an independent
environment from Striven. It lets me flex my ES6 muscles and have all
the luxuries of hot reloading, webpack loaders, and working in Vue.</p>
<p>There are definitely things that I would’ve done differently, but at
the end of the day it does what Striven needs.</p>
    ]]></content:encoded>
</item>
</channel>
</rss>
