<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>BinHong Lee&#39;s Blog</title>
    <link>https://binhong.me/blog/</link>
    <description>Recent content on BinHong Lee&#39;s Blog</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <managingEditor>binhong@binhong.me (BinHong Lee)</managingEditor>
    <webMaster>binhong@binhong.me (BinHong Lee)</webMaster>
    <lastBuildDate>Thu, 02 Apr 2026 00:00:00 -0800</lastBuildDate><atom:link href="https://binhong.me/blog/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Shifting from Code Writing to Reviewing</title>
      <link>https://binhong.me/blog/2026-04-02-shifting-from-code-writing-to-reviewing/</link>
      <pubDate>Thu, 02 Apr 2026 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2026-04-02-shifting-from-code-writing-to-reviewing/</guid>
      <description>&lt;p&gt;When I built GlobeTrotte last year, vibe-coding had just begun gaining traction but I was the &amp;ldquo;weird guy&amp;rdquo; who coded everything by hand. I spent a little over 4 weeks building the backend service (Go), iOS app (SwiftUI), Android app (Jetpack Compose), and web (TanStack) all by myself. I like to think that&amp;rsquo;s pretty fast for an app of that level of complexity. The tradeoff here however, was that I have almost no time to &amp;ldquo;handle the business side&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;I wrote about my early skepticism on AI coding &lt;a href=&#34;https://binhong.me/blog/2025-07-03-early-takes-on-vibe-coding/&#34;&gt;back in July last year&lt;/a&gt;. TLDR; I tried getting Windsurf&amp;rsquo;s SWE-1 to refactor navigation on the Android app but ended up watching it spiral into compiler errors after compiler errors as it tried to &amp;ldquo;code its way out&amp;rdquo; by piling on even more code. At the time, I concluded that vibe-coding wasn&amp;rsquo;t ready but one thing I missed was how much the model mattered.&lt;/p&gt;
        
&lt;a class=&#34;anchor&#34; href=&#34;#the-inflection-point&#34;&gt;
    &lt;h2 id=&#34;the-inflection-point&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;The inflection point&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Building &lt;a href=&#34;https://binhonglee.github.io/slogx&#34;&gt;slogx&lt;/a&gt; and &lt;a href=&#34;https://gitnav.xyz&#34;&gt;Git Navigator&lt;/a&gt; was almost an entirely opposite experience. slogx was started because I was curious to explore the capabilities of Google&amp;rsquo;s AI Studio. It definitely impressed me in terms of building good looking UI but its lack of support for &lt;em&gt;anything else&lt;/em&gt; was a bottleneck that quickly prompted me to move away (especially to build the SDKs). Git Navigator was started because I wanted to see how well Google&amp;rsquo;s Antigravity and Claude Sonnet perform. For pretty much an entire month, all my vibe-coding prompts were basically &amp;ldquo;the graph rendered wrongly in this / that case&amp;rdquo;. (I probably should&amp;rsquo;ve intervened earlier but I was lazy and curious if it can eventually figure it out.) Neither could, but Claude Opus (4.5) managed to build an actual working version. I redid the algo anyway eventually with a much simpler / straightforward idea (just DFS lol), but the fact that it worked when others didn&amp;rsquo;t, makes this an important inflection point to me.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#dev-velocity&#34;&gt;
    &lt;h2 id=&#34;dev-velocity&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Dev Velocity&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;From here on out, most of the code for both slogx and Git Navigator was written by an LLM. I do review every single line of them but that doesn&amp;rsquo;t always say much especially for areas which I&amp;rsquo;m not already &lt;em&gt;an expert&lt;/em&gt; of. It definitely &lt;em&gt;feels&lt;/em&gt; like it&amp;rsquo;d be faster than if I were to build it myself but I haven&amp;rsquo;t done actual testing to know for sure. My suspicion is that it&amp;rsquo;s faster in the first few days, maybe the first week. One month in, I can&amp;rsquo;t tell if I&amp;rsquo;m meaningfully ahead of where I&amp;rsquo;d be otherwise. The LoC isn&amp;rsquo;t dramatically higher than GlobeTrotte, but because I didn&amp;rsquo;t write this code, I&amp;rsquo;m significantly less familiar with my own codebase. That said, I can definitely see the development cycle being &lt;em&gt;significantly faster&lt;/em&gt; if I don&amp;rsquo;t actually do line-by-line code review and instead stick to just vibe-reviewing(?) it whenever I see something kinda sus.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#context-switching-tax&#34;&gt;
    &lt;h2 id=&#34;context-switching-tax&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Context switching tax&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Another important shift I noticed is the nature of work itself. Before, you&amp;rsquo;d be &amp;ldquo;wired in&amp;rdquo; while designing then coding where you would do deep focus work while holding the entire problem in your head. Now though, the prompting-and-waiting breaks the &lt;em&gt;chunk&lt;/em&gt; where you either scroll social media while &lt;em&gt;waiting&lt;/em&gt; or review and prompt a separate session in the meantime. However, this means that you&amp;rsquo;re constantly context switching between 2 or more project / features and you&amp;rsquo;re less of a &lt;em&gt;maker&lt;/em&gt; but more of a &lt;em&gt;manager&lt;/em&gt; making sure your &amp;ldquo;subordinates&amp;rdquo; complete their deliverables.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#visualizing-the-product-for-llm&#34;&gt;
    &lt;h2 id=&#34;visualizing-the-product-for-llm&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Visualizing the product for LLM&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;One of the trickier things that took me too long to figure out was visualization. Like how humans need to look at the end result (print debugging, hot reload etc.) to easily self-correct and understand what went wrong, LLMs need something like this too. If you&amp;rsquo;re building a webapp, some providers have a Chrome extension or a browser built-in for this purpose. Since I&amp;rsquo;m building a VSCode extension, I actually didn&amp;rsquo;t find much tooling around it (ironically lol). When I first made all the different coding agents build the graphing UI for Git Navigator, I spent a lot of time screenshotting and explaining what&amp;rsquo;s right / wrong with it.&lt;/p&gt;
&lt;p&gt;Eventually, I figured out to make it write a script that it can run against a given folder / repo to see how the graph is sorted. Since the rendering code would order the commits (in TypeScript objects) before passing it to be rendered as UI, the script essentially just dumps the order output for easy viewing by the LLM. This cuts me out from needing to repeatedly screenshot it as LLMs can run the script, then read the output and identify if things are ordered correctly or if there&amp;rsquo;s anything that needs fixing. I think this is probably one of my biggest lessons here, this is like an observability tool except your target audience are LLMs instead of human developers.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#catching-shortcuts&#34;&gt;
    &lt;h2 id=&#34;catching-shortcuts&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Catching shortcuts&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;There&amp;rsquo;s been a few times where I caught the model taking shortcuts that technically worked but were obviously wrong. One time, I asked for a complex git operation (some rebase gymnastic) and Opus 4.5 just used the TypeScript backend to create a temporary bash script and run that, instead of running it programmatically through the TypeScript backend. It worked fine locally on my machine (and any Unix based system I think) but it would break VS Code Remote scenarios where files aren&amp;rsquo;t local, or on Windows because path separators are wrong. It&amp;rsquo;s the kind of solution that passes every test you thought to write yet falls apart the moment a real user touches it. This is where review actually matters since LLMs &lt;em&gt;(largely)&lt;/em&gt; optimize for &amp;ldquo;does it work right now&amp;rdquo; rather than &amp;ldquo;is this the right solution&amp;rdquo;. If you&amp;rsquo;re not catching these, you&amp;rsquo;re accumulating tech debt at LLM speed.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#catching-product-gaps&#34;&gt;
    &lt;h2 id=&#34;catching-product-gaps&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Catching product gaps&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;There are also times where the feature exists and works, but the placement or interaction is off in a &amp;ldquo;product sense&amp;rdquo; kinda way. One example I had was asking Codex to implement line and hunk staging for Git Navigator. We had some good planning discussions about the tri-state checkboxes, &amp;ldquo;include/exclude&amp;rdquo; wording instead of git jargon, inline diff expansion but, it shipped the entire picker in the side panel. This makes for a weird &lt;em&gt;UX ergonomics&lt;/em&gt; because users are looking at the uncommitted changes block when they&amp;rsquo;re deciding what to commit. So burying the granular staging controls in a separate panel means most people likely wouldn&amp;rsquo;t even notice that it exists. I had to ask for it to be moved into the uncommitted changes block itself. Here&amp;rsquo;s another twist, when it did, the LLM&amp;rsquo;s first instinct was to build a new diff renderer from scratch instead of reusing the existing one used for conflict resolution. I think this is probably the &lt;em&gt;bull case&lt;/em&gt; for Product Managers being the beneficiaries of vibe-coding considering that this is likely their strong suit.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#delete-everything-and-try-again&#34;&gt;
    &lt;h2 id=&#34;delete-everything-and-try-again&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Delete everything and try again&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Fortunately, I haven&amp;rsquo;t had to rely on this strategy too much but it does happen. I think when building one of the stack features (on Git Navigator), Codex completely misunderstood what I meant and kinda just went off the rails despite being given clear specs to follow. I discarded all the changes (thanks git), started another new session, gave it the same spec, then just let it &lt;em&gt;try again&lt;/em&gt;. Funnily enough, it worked perfectly on the second run, only needed some minor tweaks before the feature is ready to be committed. I haven&amp;rsquo;t had to resort to this too much (and I don&amp;rsquo;t keep good enough git hygiene to always rely on it 🫣) but it&amp;rsquo;s definitely something to keep in mind if things go weirdly wrong.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#wrap-up&#34;&gt;
    &lt;h2 id=&#34;wrap-up&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Wrap up&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;I don&amp;rsquo;t think I&amp;rsquo;m going back to handwriting all the code (for now). Not because I&amp;rsquo;m convinced it&amp;rsquo;s faster, but because it &lt;em&gt;feels&lt;/em&gt; faster. I think it also comes with a level of detachment that makes me feel more comfortable for being ruthless with code I didn&amp;rsquo;t write while nitpicking for the ideal product experience that I want. It makes it easier to throw things away, cherry-pick only what&amp;rsquo;s good, and not get precious about any of it. Whether that&amp;rsquo;s a healthier relationship with code or just a different kind of laziness, you tell me 🫠.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Mentorship</title>
      <link>https://binhong.me/blog/2026-03-20-mentorship/</link>
      <pubDate>Fri, 20 Mar 2026 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2026-03-20-mentorship/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve done quite a bit of mentorship from short term (one session) to long term (multi-year), from high school kids to university grads or even senior engineers, so I like to think I know a thing or two about mentorship. This piece is specifically geared more towards how one should approach a longer term mentorship as a mentor.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is part of a series &lt;a href=&#34;https://binhong.me/blog/the-opinionated-engineer/&#34;&gt;(The Opinionated Engineer)&lt;/a&gt; where I share my strong opinions on engineering practices.&lt;/em&gt;&lt;/p&gt;
        
&lt;a class=&#34;anchor&#34; href=&#34;#be-transparent&#34;&gt;
    &lt;h2 id=&#34;be-transparent&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Be Transparent&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Putting this first because I personally think this is the most important. Be upfront and transparent about yourself and your advice. If you aren&amp;rsquo;t sure of something, be sure to make it clear to your mentees. Generally, you&amp;rsquo;re likely a person with authority, which is why you&amp;rsquo;re the mentor in the first place. Giving out half-baked advice would at best ruin the trust your mentees have toward you, at worst cause catastrophic career derailment to your mentees as they follow your half-baked advice. The easiest way to be transparent is to clearly share your reasoning behind arriving at the given conclusion, allowing them to raise questions or doubts if they have a differing perspective.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#understand-their-goals&#34;&gt;
    &lt;h2 id=&#34;understand-their-goals&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Understand their goals&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;This should be pretty straightforward. Not everyone tries to optimize for the highest pay or climbing the career ladder; some try to maximize learning opportunities or better work life balance. Knowing this helps you preface the advice you can provide and to help them work towards their goals. It&amp;rsquo;s also important to explain the tradeoffs of your advice: &amp;ldquo;this is a great opportunity for you to learn more but there&amp;rsquo;s always risk in working on something you&amp;rsquo;re unfamiliar with and not meeting your work expectations&amp;rdquo;.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#everyones-a-little-different&#34;&gt;
    &lt;h2 id=&#34;everyones-a-little-different&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Everyone&amp;rsquo;s a little different&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Tangential to the previous point, not everyone has to be like &amp;ldquo;you&amp;rdquo;. Generally, if they bring you a sticky situation, it can be useful to provide your perspective to help them come up with their resolution to it. But otherwise, you shouldn&amp;rsquo;t just prescribe the exact same thing to all your mentees because everyone&amp;rsquo;s a little different. The advice they receive should play to their strengths while matching their personal goals. Both of which are likely different from your own—tailor it to their needs.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#guest-lecture&#34;&gt;
    &lt;h2 id=&#34;guest-lecture&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Guest Lecture&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;In university classes, your professor might sometimes invite different industry experts to be a guest lecturer. A similar idea applies here. Sometimes your mentee needs help in an area where you&amp;rsquo;re not an expert. You likely know someone who&amp;rsquo;s good at it. If your mentee knows them, recommend they reach out to them; if not, offer to introduce them. (This applies to yourself as well. If you need help in an area you know someone is an expert in, you should reach out to them for advice.)&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#optional-dig-for-details&#34;&gt;
    &lt;h2 id=&#34;optional-dig-for-details&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;(Optional) Dig for details&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;I labeled this as optional because most people see it as the responsibility of the mentee rather than the mentor. (&amp;ldquo;I can only help those who want to be helped.&amp;rdquo;) I can see it both ways so it&amp;rsquo;s up to you to draw the line. Some mentees aren&amp;rsquo;t always very direct with their problems. They might even say that everything is going fine. But if you just poke a little, they would start sharing potential issues they foresee could materialize. Generally, this would be a pattern where they would consistently be like this, so you always need to probe a little to find out any underlying problems they might be thinking of.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#wrap-up&#34;&gt;
    &lt;h2 id=&#34;wrap-up&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Wrap up&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Mentorship isn&amp;rsquo;t about molding someone into a younger version of yourself but rather to help someone become the best version of themselves. The advice that worked for you might not work for them, and that&amp;rsquo;s okay. Be transparent about what you know and don&amp;rsquo;t know, understand what they&amp;rsquo;re actually trying to achieve, and don&amp;rsquo;t be afraid to admit when someone else would be better suited to help them. At the end of the day, good mentorship is less about having all the answers and more about asking the right questions and connecting people to the right resources. Give them what they need, not what you have.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Securing Logged Out Internal Settings</title>
      <link>https://binhong.me/blog/2026-02-08-securing-logged-out-internal-settings/</link>
      <pubDate>Sun, 08 Feb 2026 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2026-02-08-securing-logged-out-internal-settings/</guid>
      <description>&lt;p&gt;I proposed, built, and maintained the only logged out accessible tooling suite in Meta - which I&amp;rsquo;ve conveniently called &amp;ldquo;Logged Out Internal Settings&amp;rdquo; (hereafter as LOIS). This toolset is still used daily today by employees though it&amp;rsquo;s no longer as well maintained since I left the company earlier last year.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is part of a series &lt;a href=&#34;https://binhong.me/blog/project-presentation/&#34;&gt;(Project Presentation)&lt;/a&gt; where I share stories of my past projects.&lt;/em&gt;&lt;/p&gt;
        
&lt;a class=&#34;anchor&#34; href=&#34;#background&#34;&gt;
    &lt;h2 id=&#34;background&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Background&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;At this point, we were still building out &lt;a href=&#34;https://about.fb.com/news/2022/09/accounts-center-facebook-and-instagram/&#34;&gt;a brand new set of access flow that unifies the experience (for login, account recovery, registration) across Facebook, Instagram, and Messenger on both iOS and Android&lt;/a&gt;. Since the whole thing was internal anyway, people were littering internal-only information everywhere for easier debugging and faster development cycle. Between all that, I built a small tool on the landing page that let engineers quickly switch between different dev pods or prod, and another prototyping tool that lets you add any random UI for quick testing. As we got closer to release, I began thinking how to make this &amp;ldquo;launch safe&amp;rdquo;.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#problem&#34;&gt;
    &lt;h2 id=&#34;problem&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Problem&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;On a basic level, I set up a simple gating where you can only access all the internal-only stuff if you&amp;rsquo;re already connected to a dev pod. It works fine from a security perspective since there are a bunch of different checks you need to pass before you can be connected to dev pods. But this severely limited the exposure of it since employees not &lt;em&gt;already&lt;/em&gt; connected to a dev pod can&amp;rsquo;t easily utilize the tool to switch into one.&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th style=&#34;text-align: left&#34;&gt;Scenario&lt;/th&gt;
          &lt;th style=&#34;text-align: left&#34;&gt;Before&lt;/th&gt;
          &lt;th style=&#34;text-align: left&#34;&gt;After&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;prod -&amp;gt; dev pod&lt;/td&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;✅&lt;/td&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;❌&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;switch dev pod&lt;/td&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;✅&lt;/td&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;✅&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;dev pod -&amp;gt; prod&lt;/td&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;✅&lt;/td&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;✅&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;I thought this wasn&amp;rsquo;t an ideal setup because:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;prod -&amp;gt; dev pod was one of the most popular use cases&lt;/li&gt;
&lt;li&gt;dev pods are generally slower so other tools should ideally still work without it&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;But the main problem was that all these need to exist &lt;strong&gt;before you login to the app&lt;/strong&gt; so we can&amp;rsquo;t tell exactly if the user is an employee (or not).&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#ideation&#34;&gt;
    &lt;h2 id=&#34;ideation&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Ideation&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;I started with a simple security layer idea like this. Not everything is &amp;ldquo;super sensitive&amp;rdquo; and requires the highest level of security check to be accessible. Something like &amp;ldquo;show debug info&amp;rdquo; that prints out information about the device / app isn&amp;rsquo;t exactly sensitive while still extremely valuable for debugging. (Keeping it away from showing to users prevents unnecessary confusion.) This also allows for progressive increments in security access pass to gain &lt;em&gt;more&lt;/em&gt; access. For eg. you need to be on corpnet / VPN to access the dev pod connector; once connected, you&amp;rsquo;re now &amp;ldquo;promoted&amp;rdquo; to dev pod access level.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://binhong.me/blog/img/securing_lois_1.png&#34; target=&#34;_blank&#34;&gt;
    
    &lt;figure&gt;
        &lt;img src=&#34;https://binhong.me/blog/2026-02-08-securing-logged-out-internal-settings//blog/img/securing_lois_1.png&#34;&gt;
        &lt;figcaption&gt;Security layer design&lt;/figcaption&gt;
    &lt;/figure&gt;
    
&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;At this point, I started thinking of an alternative to &amp;ldquo;dev pods&amp;rdquo; for the highest level access. Some job functions (like QA, design) can benefit from this tool as well but they don&amp;rsquo;t usually have access to a dev pod. I quietly put together an idea for an access flow similar to how TOTP works. I call this the &amp;ldquo;Employee Authenticator&amp;rdquo;. More on this later.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#security&#34;&gt;
    &lt;h2 id=&#34;security&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Security&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;I found an internal discussion group on general security stuff and asked a few clarifying questions surrounding how each of these checks can / should work and what potential risks it can carry. This helps me validate the idea while also learning about some weird past incidents. Apparently there was once an incident where a public coffee shop Wi-Fi below SF Meta office was falsely flagged as corpnet because some employee gave them the wrong router for setup.&lt;/p&gt;
&lt;p&gt;Anyway, after gathering good context and information around it, I filed for a formal security review. The review went smoothly as we received the green light to proceed.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#opportunity&#34;&gt;
    &lt;h2 id=&#34;opportunity&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Opportunity&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Our org decided to host its first (rolling?) hackathon where you can form teams and build things (that you wouldn&amp;rsquo;t otherwise work on) over a given week. I polished up my proposal doc for Employee Authenticator then recruited a few engineers around me to help build it.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s how it works:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://binhong.me/blog/img/securing_lois_2.png&#34; target=&#34;_blank&#34;&gt;
    
    &lt;figure&gt;
        &lt;img src=&#34;https://binhong.me/blog/2026-02-08-securing-logged-out-internal-settings//blog/img/securing_lois_2.png&#34;&gt;
        &lt;figcaption&gt;Employee Authenticator workflow&lt;/figcaption&gt;
    &lt;/figure&gt;
    
&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;While we didn&amp;rsquo;t win anything from the hackathon, it helped kickstart development of this tooling with the idea that LOIS isn&amp;rsquo;t just for devs, but also for non-devs. I had to later get it re-reviewed because some roles (like QA) are mostly filled by &amp;ldquo;Contingent Workers&amp;rdquo; who can&amp;rsquo;t be classified as employees legally(?) so I also had it renamed to &amp;ldquo;Internal Authenticator&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: I never got through with this process entirely so if you&amp;rsquo;re a Meta employee looking at LOIS Authenticator, it&amp;rsquo;s likely all over the place since no one really took over ownership of LOIS after I left.&lt;/em&gt;&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#follow-ups&#34;&gt;
    &lt;h2 id=&#34;follow-ups&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Follow-ups&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;As I&amp;rsquo;ve mentioned at the beginning, some of these internal features already existed but they had no protection and we needed to secure them before we began testing the new flow publicly. One of the minor but important tweaks I made was to add a text label on the menu screen saying &amp;ldquo;connect to {corpnet / VPN | dev pod} to access more features&amp;rdquo;. Since engineers who&amp;rsquo;ve been working on this flow have been using these underlying features day-in-day-out, suddenly seeing them go missing can be a bit confusing. That small text saved countless hours of &amp;ldquo;Hey BinHong, I don&amp;rsquo;t see X feature anymore in LOIS.&amp;rdquo; while quickly nudging them towards the right direction to self-unblock.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#takeaways&#34;&gt;
    &lt;h2 id=&#34;takeaways&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Takeaways&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;This is an example project where I should have advocated harder for myself to gain better visibility within my org (where my performance review is done). While security teams were happy with our collaborations and many engineers (both within and outside of the org) benefitted greatly from the continued existence of LOIS, I doubt many in the management rank understood the importance of the work and underlying value it provides to engineers. From the users&amp;rsquo; (employees&amp;rsquo;) perspective, I added some layer of friction to access the same tooling; while the value it adds was to ensure we can keep this set of tooling post-public-launch.&lt;/p&gt;
&lt;p&gt;The other interesting side effect of the security layer was that it helped build a proper structure around the entire LOIS &lt;em&gt;system&lt;/em&gt;. This paved the way for an eventual &lt;em&gt;platformization&lt;/em&gt; allowing for each team to build customized toolings into LOIS.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>The Peak Bias</title>
      <link>https://binhong.me/blog/2026-01-25-the-peak-bias/</link>
      <pubDate>Sun, 25 Jan 2026 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2026-01-25-the-peak-bias/</guid>
      <description>&lt;p&gt;One of the most interesting examples is to look at any average person debating whether Jordan or LeBron is the GOAT. They will inevitably bring up Jordan&amp;rsquo;s 6-0 finals record as if Jordan only played in the NBA for 6 years. LeBron&amp;rsquo;s 4-8 record on the other hand is somehow worse than if he never made it to the finals at all (which frankly doesn&amp;rsquo;t make sense lol). This isn&amp;rsquo;t really about basketball though. It&amp;rsquo;s about a fundamental bias in how humans evaluate performance where we disproportionately value peak moments over sustained consistency, even when the consistency represents equal or greater total value.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is part of a series &lt;a href=&#34;https://binhong.me/blog/the-opinionated-engineer/&#34;&gt;(The Opinionated Engineer)&lt;/a&gt; where I share my strong opinions on engineering practices.&lt;/em&gt;&lt;/p&gt;
        
&lt;a class=&#34;anchor&#34; href=&#34;#one-big-project--many-small-projects&#34;&gt;
    &lt;h2 id=&#34;one-big-project--many-small-projects&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;One big project &amp;gt; many small projects&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Similar to &amp;ldquo;peaks&amp;rdquo;, humans have a tendency to veer towards one &amp;ldquo;big, complex&amp;rdquo; project over a bunch of smaller projects even if they require the same level of effort and expertise. We&amp;rsquo;ve all seen this happened one way or another. The person who ships one new flashy dev tool get rated as &amp;ldquo;exceed expectations&amp;rdquo; while the person who quietly shipped ten incremental performance improvements (that collectively saved more engineering time) gets a &amp;ldquo;meets all expectations&amp;rdquo;. A single monolithic launch gets remembered. Ten incremental wins that shipped over 6 months are just blurred together in everyone&amp;rsquo;s memory by the time performance review season comes around.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#the-invisible-work-trap&#34;&gt;
    &lt;h2 id=&#34;the-invisible-work-trap&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;The invisible work trap&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;This creates some really annoying incentives tbh. Nobody remembers the person who built great monitoring that prevented incidents (the incidents that never was) but everyone remembers the hero who stayed up fixing the 3am production fire (even if better testing infra could&amp;rsquo;ve prevented it in the first place, &lt;a href=&#34;https://binhong.me/blog/2025-08-01-firefighting-heroes/&#34;&gt;but we don&amp;rsquo;t talk about that lol&lt;/a&gt;). The reality is that sustained consistency is harder than occasional peaks. The person who shipped features with good code quality that never breaks in production through consistent diligence gets less credit than the person who firefights a handful of dramatic incidents (even if it might be due to their own lack of care towards shipping). Both require effort, but only one gets remembered as a &amp;ldquo;peak&amp;rdquo; moment during performance discussions.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#storytelling-as-the-workaround&#34;&gt;
    &lt;h2 id=&#34;storytelling-as-the-workaround&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Storytelling as the workaround&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;You&amp;rsquo;ll hear this advice everywhere from performance review packages to design docs to business proposals. Literally any &amp;ldquo;presentation&amp;rdquo; type stuff will tell you that storytelling matters. Storytelling is basically the skill of bundling up all your smaller projects into one big narrative to combat this bias. Instead of &amp;ldquo;shipped 10 performance improvements&amp;rdquo; you write &amp;ldquo;led reliability initiative that reduced P95 latency by 40% through systematic optimization.&amp;rdquo; Same work, just packaged differently.&lt;/p&gt;
&lt;p&gt;The uncomfortable truth is that impact without visibility might as well not exist come promo time. The people who are good at this don&amp;rsquo;t just list what they shipped but create a whole story arc like &amp;ldquo;Problem → Investigation → Solution → Impact → Future Work.&amp;rdquo; They connect the dots to show how their smaller projects collectively solved something bigger. This used to feel really inauthentic to me (and honestly sometimes it still does) but it&amp;rsquo;s a valuable skill to learn because making sure people understand what you did is part of the job. In my opinion, this is also the defining character between a &lt;em&gt;good&lt;/em&gt; manager and a &lt;em&gt;great&lt;/em&gt; manager.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#hot-take&#34;&gt;
    &lt;h2 id=&#34;hot-take&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Hot take&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Here&amp;rsquo;s the controversial part. I don&amp;rsquo;t actually think this bias is completely irrational. Peak performance (when it&amp;rsquo;s genuine) does signal important stuff. An engineer who can architect and ship a complex multi-team migration is demonstrating coordination and ambiguity navigation skills that not everyone has. These are real senior+ skills that are hard to show through smaller projects. The problem isn&amp;rsquo;t that we value peaks but that we &lt;em&gt;overvalue&lt;/em&gt; them relative to consistency. We&amp;rsquo;re also terrible at telling the difference between genuine peak performance and just being in the right place at the right time (or being good at taking credit for other people&amp;rsquo;s work lol). Sometimes the person grinding out consistent wins is actually showing way more skill, just in a less visible way.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#what-to-actually-do-about-it&#34;&gt;
    &lt;h2 id=&#34;what-to-actually-do-about-it&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;What to actually do about it&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;If you&amp;rsquo;re someone who tends toward consistent, incremental work (which is totally valid and valuable btw), here&amp;rsquo;s the reality. You need to adapt your strategy if you want recognition. The most effective move? Take on longer running projects. If you know you can consistently ship quality work, use that as a strength by committing to bigger initiatives that span months instead of weeks. Fortunately, a big long-running project basically counts as a &amp;ldquo;peak&amp;rdquo; in terms of how it gets perceived and remembered.&lt;/p&gt;
&lt;p&gt;The key is you&amp;rsquo;re not actually changing your working style since you&amp;rsquo;re still executing with the same consistency. You&amp;rsquo;re just changing scope. Instead of 10 small improvements over 6 months, you&amp;rsquo;re doing a 6-month initiative that involves 10 components. Same work, same consistency, different packaging. Other stuff that helps include actively communicate about your progress (shameless plug for &lt;a href=&#34;https://binhong.me/blog/2025-05-16-art-of-posting/&#34;&gt;my previous piece on this&lt;/a&gt;), bundling your incremental work into themes in perf reviews, and actually quantifying cumulative impact across related projects. You&amp;rsquo;re not trying to trick people into thinking your work is more valuable than it is but making sure they understand the actual value of work that&amp;rsquo;s easy to overlook (which sometimes means restructuring how you take on work in the first place).&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#but-i-kinda-just-do-whatever&#34;&gt;
    &lt;h2 id=&#34;but-i-kinda-just-do-whatever&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;But I kinda just do whatever&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Some people are &amp;ldquo;utility players&amp;rdquo; who pick up whatever project with the highest priority at the time. Usually there isn&amp;rsquo;t too much &amp;ldquo;narrative&amp;rdquo; surrounding &amp;ldquo;why do x&amp;rdquo; aside from some vague definition of it being the most urgent work at the time. In this case, you&amp;rsquo;d rely heavily on documentations and endorsements by the more senior / authoritative voices in the company to make your case. This sometimes also mean that having a &lt;em&gt;great&lt;/em&gt; manager is more important (than otherwise) when it comes to building your case for performance review. The &lt;em&gt;safe&lt;/em&gt; way is to pick a &amp;ldquo;theme&amp;rdquo; and be more intentional about the projects you commit to. Going without is viable but harder, since you rely heavily on others to build your narrative for you.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#wrap-up&#34;&gt;
    &lt;h2 id=&#34;wrap-up&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Wrap up&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;The peak bias is real and it affects everything from sports debates to software engineering careers. We remember the dramatic moments and forget sustained excellence where the person who shipped one big project gets promoted over the person who shipped ten valuable smaller ones. You can&amp;rsquo;t fight human psychology but you can work with it. If you&amp;rsquo;re the consistent incremental type, consider taking on longer projects that let you leverage that consistency at scale.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>You Can Be Both</title>
      <link>https://binhong.me/blog/2025-12-12-you-can-be-both/</link>
      <pubDate>Fri, 12 Dec 2025 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2025-12-12-you-can-be-both/</guid>
      <description>&lt;p&gt;I recently saw a post arguing that exceptional people are rare because they&amp;rsquo;re so focused on their craft that they skip &amp;ldquo;bureaucratic bullshit&amp;rdquo; and &amp;ldquo;often left other skills to rot&amp;rdquo;. &lt;a href=&#34;https://www.linkedin.com/posts/matsherman_my-take-agree-disagree-would-love-your-activity-7361597201212448769-fQHP/&#34;&gt;I left a comment explaining that it&amp;rsquo;s a 50/50 situation.&lt;/a&gt; In this piece, I&amp;rsquo;ll expand my take on this more in depth. Exceptional people comes in different shapes and forms. Pretending that someone can&amp;rsquo;t be exceptional simply because they learned to deal with the &amp;ldquo;bureaucratic bullshit&amp;rdquo; feels like a weird stereotype. (If anything, this reads like the exact type of mentality that gave birth to the &amp;ldquo;10x engineer&amp;rdquo; meme lol.)&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is part of a series &lt;a href=&#34;https://binhong.me/blog/2025-05-04-the-opinionated-engineer/&#34;&gt;(The Opinionated Engineer)&lt;/a&gt; where I share my strong opinions on engineering practices.&lt;/em&gt;&lt;/p&gt;
        
&lt;a class=&#34;anchor&#34; href=&#34;#understanding-the-game&#34;&gt;
    &lt;h2 id=&#34;understanding-the-game&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Understanding &amp;ldquo;the game&amp;rdquo;&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;There&amp;rsquo;s this weird stigma that you&amp;rsquo;re either a technically strong engineer or good at &amp;ldquo;playing politics&amp;rdquo;. These aren&amp;rsquo;t mutually exclusive. In fact, some of the best engineers I&amp;rsquo;ve worked with are good at both because they quickly learned that &amp;ldquo;playing politics&amp;rdquo; is critical to their success so they get good at it while continue to back themselves up with strong technical depths.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Being good at something doesn&amp;rsquo;t mean you love doing it.&lt;/strong&gt; Many of these great engineers would absolutely prefer to just code and solve technical challenges all day rather than spend time in planning meetings, doing stakeholder alignment, or navigating organizational politics. If you gave them the choice, they&amp;rsquo;d pick the technical work every time. But they recognize that in most environments (especially as you grow in seniority), the non-coding work is the best way to &lt;em&gt;scale your impact&lt;/em&gt; so they quickly learned to get good at it.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s like pundits complaining about modern NBA playing for the foul calls. These players are still some of the best in the world without it, but they learned that being good at it is critical to their success, so they adapt their game to it. Same thing 20 years ago with the kick-out jumper that Kobe Bryant and Ray Allen did.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#what-is-the-game&#34;&gt;
    &lt;h2 id=&#34;what-is-the-game&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;What is &amp;ldquo;the game&amp;rdquo;?&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;A lot of this comes down to understanding what your company / org is biased towards or against in their performance evaluations. Unfortunately, it&amp;rsquo;s not always what the org needs but mostly what it &lt;em&gt;thinks&lt;/em&gt; it wants. Different orgs has different priorities, speaking with a more senior engineer in the org (or observing more carefully) can show. As an example, certain orgs in Meta has very strong preference for engineers with strong technical skills so you need to be strong code reviewer and technical designer to get (a better chance) at promotion. Others prefer more tech leading skills so you need to show relevant ability - roadmap, plan, drive discussions, create scope - for a better shot at earning that promotion. Some places look for a floor raiser to help them build a 0 -&amp;gt; 1 project; others look for someone who can obsessively optimize mature products to extract maximum value. Generally, there are different archetypes that can earn you the promotion but knowing the &lt;em&gt;preferred archetype&lt;/em&gt; makes it easier for you to get it.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#play-to-your-strengths&#34;&gt;
    &lt;h2 id=&#34;play-to-your-strengths&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Play to your strengths&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;It&amp;rsquo;s like if a team signs Messi and makes him play the center back position. I&amp;rsquo;m sure he can still do fine but the team is not getting the most value out of him. Then when it&amp;rsquo;s viewed in vacuum (without context), it&amp;rsquo;d look like he&amp;rsquo;s just an average player. That&amp;rsquo;s you during performance review. Your package is generally reviewed in a vacuum without the context of &amp;ldquo;trying new stuff&amp;rdquo; or &amp;ldquo;expanding into new roles&amp;rdquo; (with the exception of joining a new team). Not playing to your strengths hurt both you and the team because the team didn&amp;rsquo;t get the most out of you, while you look like a worse engineer than your capability.&lt;/p&gt;
&lt;p&gt;If the org&amp;rsquo;s preferred &lt;em&gt;style&lt;/em&gt; is not something that matches your strength, you&amp;rsquo;d need to consider if putting in the extra work is worth it (maybe you enjoy the people + work enough), or if there are opportunities out there that suit you better. As mentioned before, what the org &lt;em&gt;needs&lt;/em&gt; isn&amp;rsquo;t necessarily what it &lt;em&gt;wants&lt;/em&gt;. You can make the case for it but it will take extra effort relative to the alternative. (That said, in many occasions, a successfully made case would allow for better &lt;em&gt;job security&lt;/em&gt; as being the &lt;em&gt;only / few&lt;/em&gt; people with such specialty.)&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#wrap-up&#34;&gt;
    &lt;h2 id=&#34;wrap-up&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Wrap up&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;It&amp;rsquo;s not that you can&amp;rsquo;t be a good engineer if you don&amp;rsquo;t fit your company&amp;rsquo;s performance rubric but rather it&amp;rsquo;d require extra effort from either you or your manager (or both) to make your case. Some are one-offs (defining a new archetype), while others might be recurring (figuring out the right business reason to justify the time and effort on your projects). At some point, you might want to ask yourself if all these extra work is worth the time and effort.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s also another school of thought that view all these as unnecessary bloats in larger tech companies. I&amp;rsquo;m not here to argue for either (I know, not very opinionated 🫣) but to share what / how to get the most &lt;em&gt;juice&lt;/em&gt; out of your work. Most people like this prefer to stick to startups where performance review processes aren&amp;rsquo;t as rigid. That&amp;rsquo;s valid too. At the end of the day, that&amp;rsquo;s just a different game with different rules. The key is knowing which game you&amp;rsquo;re playing and whether you want to keep playing it.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>FB accounts created without Messenger</title>
      <link>https://binhong.me/blog/2025-11-28-fb-accounts-without-messenger/</link>
      <pubDate>Fri, 28 Nov 2025 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2025-11-28-fb-accounts-without-messenger/</guid>
      <description>&lt;p&gt;This was one of my first experience at causing a SEV. I&amp;rsquo;ve broken production before 🫣 but not bad enough for it to be a SEV. In this situation, millions of new FB accounts were created (over the duration of the outage) without proper messaging inbox thus unable to use Messenger. I&amp;rsquo;ll dive more into how this happens below.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is part of a series &lt;a href=&#34;https://binhong.me/blog/project-presentation/&#34;&gt;(Project Presentation)&lt;/a&gt; where I share stories of my past projects.&lt;/em&gt;&lt;/p&gt;
        
&lt;a class=&#34;anchor&#34; href=&#34;#background&#34;&gt;
    &lt;h2 id=&#34;background&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Background&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;When a user tries to create a new account on FB, the system would actually first try to login with the provided credentials to see if it matches any existing account. If it matches, the user would be logged-in to their existing account instead of having a new account created. I added a new feature that make the login / matching system more complex which increases the success rate of it getting matched with an existing account (while maintaining the same low risk profile).&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://binhong.me/blog/img/fb_account_without_messenger_1.png&#34; target=&#34;_blank&#34;&gt;
    
    &lt;figure&gt;
        &lt;img src=&#34;https://binhong.me/blog/2025-11-28-fb-accounts-without-messenger//blog/img/fb_account_without_messenger_1.png&#34;&gt;
        &lt;figcaption&gt;Simple diagram showing the new change&lt;/figcaption&gt;
    &lt;/figure&gt;
    
&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;The problem however, was that the account creation process by itself was already a complex and lengthy process that takes a long time. So by adding extra processing to it (before account creation is even triggered), the entire process ended up taking so long that the request was timed-out and account creation process was halted halfway. This means accounts were created with bits-and-pieces missing, most notably it&amp;rsquo;s messaging inbox.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#discovery&#34;&gt;
    &lt;h2 id=&#34;discovery&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Discovery&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;This was discovered by an engineer in the Messenger org. They started noticing new accounts without messaging inbox and thought it was weird but they didn&amp;rsquo;t think too much of it. They just manually triggered a job to retroactively create it for those accounts. For the 2 weeks I ran my experiment (a/b test), they just ran the job manually for the subset of new users without a messaging inbox. But when I launch the experience (due to successful a/b test), things got a bit out of hand.&lt;/p&gt;
&lt;p&gt;This engineer started seeing significantly (~20x) more new users daily being created without a messaging inbox so they filed a SEV. At some point, someone was able to pinpoint the angle change of their graph with my experiment launch. Seeing as my project directly affects the way account creation process works, it&amp;rsquo;s very likely to be related, thus I got pulled in.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#verification--resolution&#34;&gt;
    &lt;h2 id=&#34;verification--resolution&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Verification + Resolution&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;I was initially skeptical at their reasoning around why these new accounts are missing the messaging inbox and thought there might be something else. But to prove it either way, I decided to pull the plug and unship the project to see if that solves the issue. &lt;strong&gt;It did.&lt;/strong&gt; I was wrong but now we need to figure out (ideally) if there&amp;rsquo;s a way to fix this while still shipping the project. We pulled in more help from people who better knows how new account creation works to see if there are other things broken (aside from inbox).&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://binhong.me/blog/img/fb_account_without_messenger_2.png&#34; target=&#34;_blank&#34;&gt;
    
    &lt;figure&gt;
        &lt;img src=&#34;https://binhong.me/blog/2025-11-28-fb-accounts-without-messenger//blog/img/fb_account_without_messenger_2.png&#34;&gt;
        &lt;figcaption&gt;Simple diagram showing the fix&lt;/figcaption&gt;
    &lt;/figure&gt;
    
&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;As far as we can tell, only inbox was affected by this. So we essentially moved messaging inbox creation to be triggered earlier (as one of the top-tier jobs to be ran in parallel). We re-tested it and see this fixes the issue for all but one or two interfaces (I think it was FBLite and maybe mWeb that still had issues). I actually kept the SEV in &lt;code&gt;Cleanup&lt;/code&gt; state for a bit until we dropped it off as the team (technically just half the team which included me) had a prioritization shift moving away from growth to 0 -&amp;gt; 1 so we just left it as is.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Eventually, the whole account creation flow was replaced anyway as part of the 0 -&amp;gt; 1 effort to launch &lt;a href=&#34;https://about.fb.com/news/2022/09/accounts-center-facebook-and-instagram/&#34;&gt;the new unified experience&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#aftermath&#34;&gt;
    &lt;h2 id=&#34;aftermath&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Aftermath&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;I was made the SEV owner (being the person who caused it) and filled out the SEV report. I don&amp;rsquo;t remember too clearly how the review went anymore but I did know that I was really nervous throughout. I learnt a lot from the process and as far as I know, I was not penalized for it in my performance review seeing it wasn&amp;rsquo;t exactly &lt;em&gt;my&lt;/em&gt; failure that caused this (more on &lt;a href=&#34;https://binhong.me/blog/2025-05-30-no-blame-sev-culture/&#34;&gt;No Blame SEV Culture&lt;/a&gt;).&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#takeaway&#34;&gt;
    &lt;h2 id=&#34;takeaway&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Takeaway&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;The main takeaway here is that metric monitoring and analysis only goes so far in making sure things work. It generally doesn&amp;rsquo;t cover things that are affected outside of your immediate team&amp;rsquo;s scope. Secondly, even if skeptical, don&amp;rsquo;t assume without proof. I was skeptical that my experiment was the rootcause but I&amp;rsquo;m glad I committed to verifying it anyway (which proved myself wrong). Stressful at the time, but good learning experience overall.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Incident Review Process</title>
      <link>https://binhong.me/blog/2025-11-14-incident-review-process/</link>
      <pubDate>Fri, 14 Nov 2025 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2025-11-14-incident-review-process/</guid>
      <description>&lt;p&gt;In the &lt;a href=&#34;https://binhong.me/blog/2025-07-25-major-incident-runbook/&#34;&gt;previous runbook&lt;/a&gt;, I mostly focused on things to do &lt;em&gt;during&lt;/em&gt; an incident as it happens. This week, we will focus more on what to do &lt;em&gt;after&lt;/em&gt; the incident is resolved. This is an opportunity to look back at what went well, what went wrong, and how we can best prepare ourselves or prevent something similar from happening again in the future.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is part of a series &lt;a href=&#34;https://binhong.me/blog/2025-05-04-the-opinionated-engineer/&#34;&gt;(The Opinionated Engineer)&lt;/a&gt; where I share my strong opinions on engineering practices.&lt;/em&gt;&lt;/p&gt;
        
&lt;a class=&#34;anchor&#34; href=&#34;#report&#34;&gt;
    &lt;h2 id=&#34;report&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Report&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;After the incident, the incident owner should fill out an incident report detailing things like root cause, alerts, remediation, recovery, prevention, etc. Ideally this is made into a template and the author should go into detail about what actually happened vs what should have happened and how it could be improved. The report should be completed at least a day or two ahead of the review to allow for optional pre-reads.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#review&#34;&gt;
    &lt;h2 id=&#34;review&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Review&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Usually you&amp;rsquo;d need someone (or a group of people taking turns) to organize the review. Their job is to pick the set of incidents to review, make sure that incident owners fill out the report adequately, and that everyone relevant is invited and available to attend the review. It&amp;rsquo;s not really a &lt;em&gt;hard&lt;/em&gt; job per se but it requires some async communication and some level of familiarity with the org scope to ensure that all the right people involved in each incident will be there at the review.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#audience&#34;&gt;
    &lt;h2 id=&#34;audience&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Audience&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Aside from incident owners and people directly involved in said incidents, team EMs, senior engineers, and senior org leaders (Director / VP) are critical to have present in the review. EMs are there to ensure visibility and awareness of the importance of follow-up work from the incident review. Senior engineers are there to raise questions and/or provide recommendations especially surrounding detection and prevention strategies. Senior org leaders showing up - at the very least - solidify the importance of the review and enforce that planned follow-ups are actually worked on with allocated time instead of being set aside / deprioritized.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#follow-ups&#34;&gt;
    &lt;h2 id=&#34;follow-ups&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Follow-ups&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;After the review, there will likely be a laundry list of follow-up work to be done. Note them down in your task tracker (Jira, Asana etc.) and assign them to the right owners. By default, the incident owner would own it but it&amp;rsquo;s very common to have it assigned to peers or even external teams, depending on how/what was broken. One of the more common failure modes here is the lack of tracking on these follow-up tasks. Make sure to have a proper process to (at least) have someone - &lt;em&gt;anyone&lt;/em&gt; - review these tasks over time to ensure they are being worked on in a timely manner and actually completed or attempted in good faith. It&amp;rsquo;d be a shame (with a lot of awkward conversation) if there ended up being another similar incident that could have been prevented by said follow-up task.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#pattern&#34;&gt;
    &lt;h2 id=&#34;pattern&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Pattern&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;In some cases, frequent attendees might notice a pattern of incidents and propose wider improvements (instead of specifics to any incident). One example we ran into was how a lot of incidents could have been prevented by having automated tests. This wasn&amp;rsquo;t because the team didn&amp;rsquo;t make an effort to write them, but because there was a more underlying issue (outside of the team&amp;rsquo;s direct purview) causing existing tests to become flaky. We ended up having an entire workstream focused on fixing all the automated test blockers found in the org. (I&amp;rsquo;ll probably write about my experience working on it someday, under the &lt;a href=&#34;https://binhong.me/blog/project-presentation&#34;&gt;project presentation&lt;/a&gt; series.) Anyway, the point here is that having a consistent set of decision-making audience helps make sure patterns like these can be better discovered instead of overlooked.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#accountability&#34;&gt;
    &lt;h2 id=&#34;accountability&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Accountability&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;I&amp;rsquo;ve touched on this previously in &lt;a href=&#34;https://binhong.me/blog/2025-05-30-no-blame-sev-culture#no-blame--no-responsibility&#34;&gt;No Blame SEV (Incident) Culture&lt;/a&gt;. Generally, there shouldn&amp;rsquo;t be any explicit &lt;em&gt;blaming&lt;/em&gt; happening in the incident review meeting but as I&amp;rsquo;ve seen others put it, there could still be &lt;em&gt;&amp;ldquo;shame&amp;rdquo;&lt;/em&gt;. The way I&amp;rsquo;ve understood how a lot of this works is that the worse the outage was, the less likely the incident is to be entirely the responsibility of an individual. If you took down the &lt;em&gt;entire&lt;/em&gt; production tier of your company, there are likely a lot of things that need to go wrong for it to happen, so it shouldn&amp;rsquo;t all be on you.&lt;/p&gt;
&lt;p&gt;Aside from all the &amp;ldquo;who&amp;rsquo;s wrong / who&amp;rsquo;s to blame&amp;rdquo; side of accountability, there&amp;rsquo;s also the part where each responsible team needs to take on follow-up tasks to strengthen their end of reliability to prevent similar incidents from happening again. For example, if an internal framework or component library change breaks a bunch of stuff in the company, the most obvious thing would be for the framework/library team to improve their regression testing. But client teams might also want to consider adding stricter test coverage on critical paths so they can be more defensive and catch potential future breakages earlier.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#wrap-up&#34;&gt;
    &lt;h2 id=&#34;wrap-up&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Wrap up&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;The incident review process is a critical part of your team&amp;rsquo;s long-term reliability. It can be daunting to those presenting their first incidents so a proper report template helps make sure you always have quality content for the review. From there, getting the right people in the room regularly and actually tracking follow-up work helps make sure you don&amp;rsquo;t fall for the same problem again. Even if you aren&amp;rsquo;t sure where to start, putting together a rough process can help kickstart it as you re-evaluate and refine it as it goes. The goal isn&amp;rsquo;t to eliminate all incidents, but to make sure you&amp;rsquo;re learning from them and getting stronger as a result.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Leaked `FakeIncorrectPassword`</title>
      <link>https://binhong.me/blog/2025-10-31-leaked-fakeincorrectpassword/</link>
      <pubDate>Fri, 31 Oct 2025 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2025-10-31-leaked-fakeincorrectpassword/</guid>
      <description>&lt;p&gt;It was Thanksgiving day and I was mostly chilling at home scrolling Threads while watching YouTube. I then came across this post:&lt;/p&gt;
&lt;div class=&#34;threads_wrapper&#34;&gt;
  &lt;a href=&#34;https://www.threads.net/@justinamphlett/post/DC7O6MHOXaX&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34; class=&#34;threads&#34;&gt;
    &lt;p class=&#34;handle&#34;&gt;@justinamphlett&lt;/p&gt;
    &lt;svg class=&#34;logo&#34; aria-label=&#34;Threads&#34; viewBox=&#34;0 0 192 192&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;
      &lt;path d=&#34;M141.537 88.9883C140.71 88.5919 139.87 88.2104 139.019 87.8451C137.537 60.5382 122.616 44.905 97.5619 44.745C97.4484 44.7443 97.3355 44.7443 97.222 44.7443C82.2364 44.7443 69.7731 51.1409 62.102 62.7807L75.881 72.2328C81.6116 63.5383 90.6052 61.6848 97.2286 61.6848C97.3051 61.6848 97.3819 61.6848 97.4576 61.6855C105.707 61.7381 111.932 64.1366 115.961 68.814C118.893 72.2193 120.854 76.925 121.825 82.8638C114.511 81.6207 106.601 81.2385 98.145 81.7233C74.3247 83.0954 59.0111 96.9879 60.0396 116.292C60.5615 126.084 65.4397 134.508 73.775 140.011C80.8224 144.663 89.899 146.938 99.3323 146.423C111.79 145.74 121.563 140.987 128.381 132.296C133.559 125.696 136.834 117.143 138.28 106.366C144.217 109.949 148.617 114.664 151.047 120.332C155.179 129.967 155.42 145.8 142.501 158.708C131.182 170.016 117.576 174.908 97.0135 175.059C74.2042 174.89 56.9538 167.575 45.7381 153.317C35.2355 139.966 29.8077 120.682 29.6052 96C29.8077 71.3178 35.2355 52.0336 45.7381 38.6827C56.9538 24.4249 74.2039 17.11 97.0132 16.9405C119.988 17.1113 137.539 24.4614 149.184 38.788C154.894 45.8136 159.199 54.6488 162.037 64.9503L178.184 60.6422C174.744 47.9622 169.331 37.0357 161.965 27.974C147.036 9.60668 125.202 0.195148 97.0695 0H96.9569C68.8816 0.19447 47.2921 9.6418 32.7883 28.0793C19.8819 44.4864 13.2244 67.3157 13.0007 95.9325L13 96L13.0007 96.0675C13.2244 124.684 19.8819 147.514 32.7883 163.921C47.2921 182.358 68.8816 191.806 96.9569 192H97.0695C122.03 191.827 139.624 185.292 154.118 170.811C173.081 151.866 172.51 128.119 166.26 113.541C161.776 103.087 153.227 94.5962 141.537 88.9883ZM98.4405 129.507C88.0005 130.095 77.1544 125.409 76.6196 115.372C76.2232 107.93 81.9158 99.626 99.0812 98.6368C101.047 98.5234 102.976 98.468 104.871 98.468C111.106 98.468 116.939 99.0737 122.242 100.233C120.264 124.935 108.662 128.946 98.4405 129.507Z&#34; fill=&#34;currentColor&#34;/&gt;
    &lt;/svg&gt;&lt;p class=&#34;description&#34;&gt;
      Found a pretty horrific dark pattern with Instagram. If you try to log in on mobile web, it fails with ‘incorrect password’. I double check… it’s right, it’s just not working. Gave up, installed the app, password is fine. Tried again today just to see… this time I get the following error message. Looks like a translation key is missing and it’s serving me the backend name for it? FakeIncorrectPassword ?? Are you shitting me?
    &lt;/p&gt;&lt;div class=&#34;image&#34;&gt;
      &lt;img src=&#34;https://binhong.me/blog/2025-10-31-leaked-fakeincorrectpassword//blog/624099990_18059047814338384_6804582354372308281_n_2407761658502399090.jpg&#34; alt=&#34;Post image&#34; loading=&#34;lazy&#34;&gt;
    &lt;/div&gt;&lt;/a&gt;
&lt;/div&gt;
  
&lt;p&gt;I immediately recognized that the screenshot is bad. &lt;code&gt;FakeIncorrectPassword&lt;/code&gt; is an internal error code used when the integrity system thinks you&amp;rsquo;re a bad actor (bot, scraper, credential stuffer etc) and so we would show you an &lt;em&gt;Incorrect Password&lt;/em&gt; error message regardless of whether the password is correct. But as seen in the screenshot, we show the raw &lt;code&gt;FakeIncorrectPassword&lt;/code&gt; string directly instead of the regular &lt;em&gt;Incorrect Password&lt;/em&gt; error.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is part of a series &lt;a href=&#34;https://binhong.me/blog/project-presentation/&#34;&gt;(Project Presentation)&lt;/a&gt; where I share stories of my past projects.&lt;/em&gt;&lt;/p&gt;
        
&lt;a class=&#34;anchor&#34; href=&#34;#notifying-owners&#34;&gt;
    &lt;h2 id=&#34;notifying-owners&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Notifying owners&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;The user noted that this happens on mobile web which means they happened to land on a test group, since a sister team had just started public testing this new UI / flow on mobile web. I tried ringing up the oncall for their team (who also happened to be the owner of the public test) but I saw that they had actually marked themselves out-of-office for the whole weekend through the upcoming Monday. Well, that&amp;rsquo;s not good (also weird that they were off while oncall❓).&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://binhong.me/blog/img/fakeincorrectpassword1.png&#34; target=&#34;_blank&#34;&gt;
    
    &lt;figure&gt;
        &lt;img src=&#34;https://binhong.me/blog/2025-10-31-leaked-fakeincorrectpassword//blog/img/fakeincorrectpassword1.png&#34;&gt;
        &lt;figcaption&gt;Comparison between old vs new login screen&lt;/figcaption&gt;
    &lt;/figure&gt;
    
&lt;/a&gt;
&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#investigation&#34;&gt;
    &lt;h2 id=&#34;investigation&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Investigation&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Anyway, I had a few hours to kill before I needed to leave to join my friends&amp;rsquo; Thanksgiving party. I looked at the time and told myself &lt;em&gt;&amp;ldquo;I have time&amp;rdquo;&lt;/em&gt; so I decided to start debugging it myself. I&amp;rsquo;d worked tangentially with this stuff before so I knew a few other things (like 2FA) that also need special handling should be working as intended. My hunch told me that this was caused by the gap between the Python stack (IG server) throwing some error that wasn&amp;rsquo;t properly consumed by the Hack server stack (that determines the UI). So I searched for &lt;code&gt;FakeIncorrectPassword&lt;/code&gt; in the Python stack and found something like &lt;code&gt;TwoFactorError&lt;/code&gt;. Then, I looked for &lt;code&gt;TwoFactorError&lt;/code&gt; in the Hack stack to see how it works and eventually found the one that didn&amp;rsquo;t handle the case for &lt;code&gt;FakeIncorrectPassword&lt;/code&gt;. Simple enough, I just had to add more stuff (iirc I added a few others as well as the aforementioned &lt;code&gt;FakeIncorrectPassword&lt;/code&gt;) to the existing switch case.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://binhong.me/blog/img/fakeincorrectpassword2.png&#34; target=&#34;_blank&#34;&gt;
    
    &lt;figure&gt;
        &lt;img src=&#34;https://binhong.me/blog/2025-10-31-leaked-fakeincorrectpassword//blog/img/fakeincorrectpassword2.png&#34;&gt;
        &lt;figcaption&gt;Simple diagram to show how the calls relate&lt;/figcaption&gt;
    &lt;/figure&gt;
    
&lt;/a&gt;
&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#testing&#34;&gt;
    &lt;h2 id=&#34;testing&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Testing&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;At first, I tried triggering the &lt;code&gt;FakeIncorrectPassword&lt;/code&gt; error on my devserver to test the fix but I don&amp;rsquo;t really know how most of these things work (having never worked on integrity teams). I eventually figured I could just comment out the integrity system calls and just always force return &lt;code&gt;FakeIncorrectPassword&lt;/code&gt; error. Trigger login and take a screenshot for both before and after the fix. Done! I sent the fix out for review and waited for a bit to make sure everything turned green on CI before leaving for the Thanksgiving party (didn&amp;rsquo;t want to bring my laptop with me since I wasn&amp;rsquo;t actually oncall lol).&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#shipping&#34;&gt;
    &lt;h2 id=&#34;shipping&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Shipping&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Before heading out, I tagged 2 other oncalls (on top of the OOO oncall) to let them know that I&amp;rsquo;d written the fix and I&amp;rsquo;d let them decide if they wanted to ship it or wait till Monday. One of them saw it and immediately approved + shipped my fix. Monday came around and my change got the attention of a few people from the security team. They eventually filed a post-hoc SEV for this and did an in-depth review on what went wrong in the process that led to the leak of this error message.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#takeaway&#34;&gt;
    &lt;h2 id=&#34;takeaway&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Takeaway&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;The main culprit here is that most Instagram code lives in a different stack (Python) than where the UI is (Hack) and a lot of the Python code just uses &lt;code&gt;string&lt;/code&gt; everywhere instead of proper enums for something like this, making it really hard to keep track of all the potential edge cases that could be missed. Having some familiarity with both the Python and the Hack stacks made it easier to trace the problem and spot the gap in the error handling logic. The fix itself was straightforward once I found the right place, but getting there required understanding how errors flow between multiple systems.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>The Myth of Technical Complexity</title>
      <link>https://binhong.me/blog/2025-10-17-the-myth-of-technical-complexity/</link>
      <pubDate>Fri, 17 Oct 2025 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2025-10-17-the-myth-of-technical-complexity/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve seen multiple different people fail their promotions due to &amp;ldquo;lack of technical complexity&amp;rdquo; in their work. While some of this rings true, more often than not, the underlying technical complexity of their work is not properly understood because - &lt;em&gt;ironically&lt;/em&gt; - they cut through it smoothly as strong technical engineers. Unless you were doing code / design reviews for them, you wouldn&amp;rsquo;t notice it. On the flip side, I&amp;rsquo;ve had projects listed as my capstone &amp;ldquo;technical complexity&amp;rdquo; project of the year that &lt;em&gt;imo&lt;/em&gt; wasn&amp;rsquo;t very complex compared to some other stuff I needed to work on. Why is this happening and how do I make sure my work is properly valued?&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is part of a series &lt;a href=&#34;https://binhong.me/blog/2025-05-04-the-opinionated-engineer/&#34;&gt;(The Opinionated Engineer)&lt;/a&gt; where I share my strong opinions on engineering practices.&lt;/em&gt;&lt;/p&gt;
        
&lt;a class=&#34;anchor&#34; href=&#34;#the-problem&#34;&gt;
    &lt;h2 id=&#34;the-problem&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;The Problem&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;During your performance or promotion review, the audience generally includes your manager, their peers, org leaders, and &lt;em&gt;sometimes&lt;/em&gt; a couple of very senior ICs in your org. Realistically, none of them have seen your code (though some might have seen your project design docs). So how could they &lt;em&gt;know&lt;/em&gt; if something is technically complex? &lt;strong&gt;They don&amp;rsquo;t.&lt;/strong&gt; Instead, they mostly rely on their intuition (based on what they know of the project, or just the project name alone), or they hinge it on other calibrated agreements (more on this later). If your project name is &amp;ldquo;Logging for X,&amp;rdquo; it&amp;rsquo;s very likely that it will be challenged for its technical complexity level for any senior engineer because &amp;ldquo;how hard could logging be?&amp;rdquo; (Turns out, not only can they be hard, but they could also be wrongly designed with long-term negative side effects, especially if your leaders can&amp;rsquo;t get out of the sunk-cost fallacy. But that&amp;rsquo;s a story for another day.)&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#just-a-one-line-fix&#34;&gt;
    &lt;h2 id=&#34;just-a-one-line-fix&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Just a one-line fix&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Sometimes a simple bug is masked by convoluted layers of abstractions, making it extremely hard to discover. We&amp;rsquo;ve all had bugs like this where everyone was at a loss as to why it was broken, but it turned out to be a simple one-liner fix. If you look at the fix diff alone, it would look straightforward and you&amp;rsquo;d wonder why no one noticed this earlier. These are technically complex to debug from a given stack trace (or sometimes just screenshots) to the actual root cause. In fact, this is the definition of work for a staff engineer archetype - &lt;em&gt;fixer&lt;/em&gt;. Yet, this is also an example of technically complex work that&amp;rsquo;s commonly overlooked due to how deceptively easy it looks.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#upfront-alignment&#34;&gt;
    &lt;h2 id=&#34;upfront-alignment&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Upfront alignment&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;The easiest way to prevent later disagreement on the complexity nature of your work would be to align it upfront. Talk to your TL / EM to make sure they agree with your assessment of how complex the work really is. (Excluding PMs here because they generally don&amp;rsquo;t have strong opinions / say on the technical complexity of your work.) Even if you end up disagreeing, you&amp;rsquo;d know exactly what you&amp;rsquo;re getting yourself into ahead of the project and decide accordingly (depending on options available to you). For most junior engineers and some early senior engineers, this is likely something that happened even prior to you knowing about the project. The senior engineer who assigned you the project likely aligned this upfront to make sure the project fits your growth path (meeting expectations / promo) your manager has in mind for you.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#similar-past-attempts&#34;&gt;
    &lt;h2 id=&#34;similar-past-attempts&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Similar past attempts&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Another way you could demonstrate the technical complexity of a project is by citing previous failures at attempting this project. Generally, this requires a project to have been attempted previously (even if there are slight differences in details). It&amp;rsquo;s important to keep in mind that you should always try to do things &lt;em&gt;only you can do&lt;/em&gt; (maybe I&amp;rsquo;ll write more about it in the future) and delegate everything else. Someone else failing previously is essentially a way to show that &amp;lsquo;you&amp;rsquo; were the key ingredient to making the project a success.&lt;/p&gt;
&lt;p&gt;If there&amp;rsquo;s a similar project that was previously executed successfully, that&amp;rsquo;s a slightly different story. It can still be complex, but the complexity level will likely be reduced by at least 1 level (if not more) depending on how similar they are. The idea is that you already have a good reference that helped scope out a lot of the &lt;em&gt;unknown unknowns&lt;/em&gt;, making this simpler.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#borrowing-an-authority&#34;&gt;
    &lt;h2 id=&#34;borrowing-an-authority&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Borrowing an authority&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Similar to what I previously wrote in &lt;a href=&#34;https://binhong.me/blog/2025-05-16-art-of-posting/#pre-read-from-subject-matter-experts&#34;&gt;The Art of Posting&lt;/a&gt; and what Mekka wrote &lt;a href=&#34;https://mekka-tech.com/posts/2018-08-09-the-difficulty-anchor/&#34;&gt;here about The Difficulty Anchor&lt;/a&gt;, you can also borrow the authority of someone else more senior in your team or org to endorse your evaluation of the project. This is especially important in areas where you&amp;rsquo;re not seen as an expert. While it&amp;rsquo;s ideal to get this on the record early on (as mentioned in the &lt;a href=&#34;#upfront-alignment&#34;&gt;earlier section&lt;/a&gt;), this can still be done post-hoc as well. This is especially important when you run into unexpected complexity as you work through the project. Having someone credible validate that &amp;ldquo;yeah, this turned out to be way messier than we thought&amp;rdquo; carries much more weight than you making that case yourself.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#wrap-up&#34;&gt;
    &lt;h2 id=&#34;wrap-up&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Wrap up&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;The cruel irony of engineering is that doing your job well often makes it invisible. When you smoothly navigate complex technical challenges, stakeholders see the outcome, not the expertise required to get there. Don&amp;rsquo;t let your competence work against you. The technical complexity of your work needs to be communicated, not just demonstrated. Whether it&amp;rsquo;s aligning expectations upfront, citing similar past failures, or borrowing authority from senior engineers, make sure the people evaluating you understand what they&amp;rsquo;re really looking at.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Shipping a project with -2M MAP</title>
      <link>https://binhong.me/blog/2025-10-03-shipping-negative-2m-map/</link>
      <pubDate>Fri, 03 Oct 2025 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2025-10-03-shipping-negative-2m-map/</guid>
      <description>&lt;p&gt;There&amp;rsquo;s a saying in Meta that &amp;lsquo;Nothing is somebody else&amp;rsquo;s problem&amp;rsquo; but sometimes, it really is. At this point in time, I was working on a growth team whose top-line goal was to grow Facebook MAP (monthly active people). So naturally, it&amp;rsquo;s surprising for me to have shipped something that directly regressed the very metric the team should be growing.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is part of a series &lt;a href=&#34;https://binhong.me/blog/project-presentation/&#34;&gt;(Project Presentation)&lt;/a&gt; where I share stories of my past projects.&lt;/em&gt;&lt;/p&gt;
        
&lt;a class=&#34;anchor&#34; href=&#34;#background&#34;&gt;
    &lt;h2 id=&#34;background&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Background&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;This was still within the first year of me joining the team. Unfortunately, up to this point, I hadn&amp;rsquo;t had any successful (+MAP) launches, and I&amp;rsquo;d been eager to prove myself. The most recent project I worked on before this was to show an educational screen to users right after they logged in (under a specific condition) before they got redirected to newsfeed. Once a user logs an impression on newsfeed, they are considered MAP.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://binhong.me/blog/img/negative_2m_map_1.png&#34; target=&#34;_blank&#34;&gt;
    
    &lt;img src=&#34;https://binhong.me/blog/2025-10-03-shipping-negative-2m-map//blog/img/negative_2m_map_1.png&#34;&gt;
    
&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;My project failed because for some reason it logged a MAP increase on the very first day - which didn&amp;rsquo;t make sense because users should be MAP on the first day regardless of whether they&amp;rsquo;re in the test or control group. If anything, it could have had a slight drop if users dropped off on the interstitial before they reached newsfeed. At this point, my manager suggested that we move on to something else since no one really knew why. I was, however, adamant about figuring out what happened, so I started digging into it anyway. (&lt;strong&gt;major mistake&lt;/strong&gt;)&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#the-bug&#34;&gt;
    &lt;h2 id=&#34;the-bug&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;The Bug&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;I dug through the stacktrace step-by-step, the code line-by-line, until I saw a variable that caught my eye: &lt;code&gt;is_platform_login&lt;/code&gt;. For some reason, when it was true, we ignored the &lt;code&gt;next&lt;/code&gt; param in redirection. I didn&amp;rsquo;t know what &amp;ldquo;platform login&amp;rdquo; was, so it was time to get some help. I asked my mentor / tech lead what &amp;ldquo;platform login&amp;rdquo; was and was told that it&amp;rsquo;s third-party login (think &amp;ldquo;Login with Facebook&amp;rdquo; on other sites). It&amp;rsquo;s something owned by a separate team, and I should go ask this specific person (Kevin) if I had more questions about it. So I decided to test how it worked. I opened up Spotify, clicked &amp;ldquo;Login with Facebook,&amp;rdquo; then logged in (through that specific condition), and thereafter I was redirected to FB Newsfeed instead of going back to Spotify to complete my login!&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#discovering-the-scope&#34;&gt;
    &lt;h2 id=&#34;discovering-the-scope&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Discovering the scope&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;First, I looked up the blame and just asked the person (on a sister team) why they explicitly excluded platform login from this redirection. They told me that&amp;rsquo;s just how the team tests and ships new features in general. Fair, but I didn&amp;rsquo;t think it made sense in this case. So I reached out to Kevin and asked if they were aware of the bug. They were surprised and agreed with my assessment that it should be fixed.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://binhong.me/blog/img/negative_2m_map_2.png&#34; target=&#34;_blank&#34;&gt;
    
    &lt;img src=&#34;https://binhong.me/blog/2025-10-03-shipping-negative-2m-map//blog/img/negative_2m_map_2.png&#34;&gt;
    
&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;I merged the change and started an experiment for the fix. After a while, I pulled the metrics on the experiment and saw that it regressed MAP heavily, around 2M MAP. This wasn&amp;rsquo;t exactly surprising, as you&amp;rsquo;ve seen in the image above, since we were redirecting the users back to the third-party platform source, which no longer counted them as MAP. I went back to Kevin and asked what their team&amp;rsquo;s goal metrics were and if they had a data scientist on their team willing to help with experiment analysis.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#justifying-the-ship&#34;&gt;
    &lt;h2 id=&#34;justifying-the-ship&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Justifying the ship&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;We brought the experiment to be reviewed. (If you aren&amp;rsquo;t familiar, &lt;a href=&#34;https://binhong.me/blog/2025-06-27-experiment-review-process/&#34;&gt;here&amp;rsquo;s a previous article on how experiment review works&lt;/a&gt;.) Most people understood the reasoning behind the ship recommendation, but there were some questions around better understanding whether this was intended user behavior. After some back-and-forth, we agreed to ship this with a long-term holdout to study the potential long-term MAP effect.&lt;/p&gt;
&lt;p&gt;A few months later, we pulled the data for the long-term holdout and verified that the long-term MAP regression was closer to 20k. This meant that 90% of the users who were &lt;em&gt;forced&lt;/em&gt; to newsfeed (caused by the &lt;em&gt;bug&lt;/em&gt;) did not stay MAP for very long and eventually churned one way or another anyway. It&amp;rsquo;s likely because these users did not intent to &lt;em&gt;actually use&lt;/em&gt; Facebook to begin with, thus also unlikely to stick around when they were &lt;em&gt;wrongly&lt;/em&gt; redirected.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#takeaway&#34;&gt;
    &lt;h2 id=&#34;takeaway&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Takeaway&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;For one, I was penalized for essentially spending too much time on work that wasn&amp;rsquo;t aligned with the team I was on. Realistically, I should have taken my manager&amp;rsquo;s advice to move on to another project. Even after I&amp;rsquo;d found the bug, I should have passed this off to Kevin and their team to run the experiment and provided support instead of trying to be the hero. The results from the long-term holdout also didn&amp;rsquo;t materialize until the next performance review cycle, so that definitely didn&amp;rsquo;t help.&lt;/p&gt;
&lt;p&gt;Finally, I still never actually figured out whether this was the bug that was causing my original project to fail. We suspect it might be related, but I never re-ran it and never got any sort of verification that it solved the issue. It was a lot of learning experience for me overall, but at least I delivered (imo) the ideal user experience and was able to justify the change &lt;em&gt;despite&lt;/em&gt; the team&amp;rsquo;s goal metrics.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Communicating Effectively</title>
      <link>https://binhong.me/blog/2025-09-19-communicating-effectively/</link>
      <pubDate>Fri, 19 Sep 2025 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2025-09-19-communicating-effectively/</guid>
      <description>&lt;p&gt;It’s a common problem at work where you see a problem but you’re having a hard time trying to convince people to support your attempt at fixing it. Worse still, they might not even see it as a problem (or they don’t think it’s something that warrants any attention from anyone). I’m approaching this piece mostly from an &lt;em&gt;“I need my leadership / partners to understand what I do is important”&lt;/em&gt; perspective. But the ideology can similarly be used on other occasions like selling an idea to your peers etc. Ideally you start thinking about this as you try to get the green light to pursue your project instead of attempting to justify it &lt;em&gt;after&lt;/em&gt; you’ve already invested your time into it with little to show for it.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is part of a series &lt;a href=&#34;https://binhong.me/blog/2025-05-04-the-opinionated-engineer/&#34;&gt;(The Opinionated Engineer)&lt;/a&gt; where I share my strong opinions on engineering practices.&lt;/em&gt;&lt;/p&gt;
        
&lt;a class=&#34;anchor&#34; href=&#34;#understand-your-audience&#34;&gt;
    &lt;h2 id=&#34;understand-your-audience&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Understand your audience&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Whoever you’re talking to, they have their own perspective and priorities. They care about certain things. Make sure to understand that and position your proposal from &lt;em&gt;their&lt;/em&gt; perspective. One common communication gap is where someone keeps trying to tell their leaders &lt;em&gt;“how hard they / the team has been working”&lt;/em&gt; - which is cool and all - but your leaders likely want to know more about &lt;em&gt;“what’s in it for me?”&lt;/em&gt;. For example, instead of telling your manager that “I put in 60 hours a week all of last month to make this happen,” it should be more like “I shipped x MAU which is x% of the team / org goal” or “I worked overtime to handle multiple unexpected surprises (have documentation for this) to ensure that we can ship / launch / test {org priority project} in a timely manner.” Changing the framing of your statement (by empathizing with your audience) makes it significantly more appealing to your target audience.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#strategic-positioning&#34;&gt;
    &lt;h2 id=&#34;strategic-positioning&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Strategic positioning&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;When you pitch a project proposal, you need strong reasoning on why you think it will be worth the time investment. In most cases, you’d be making the case using some sort of metrics or goal that the project is expected to improve. However, that’s not always the case. If you have a goal metric to chase but this project doesn’t directly affect that, think of how it’s affected indirectly. In my previous piece on dev tool valuation, I mentioned &lt;a href=&#34;https://binhong.me/blog/2025-07-18-understanding-value-of-dev-tools/#cost-of-engineers&#34;&gt;engineering cost&lt;/a&gt; being the most straightforward measurement available.&lt;/p&gt;
&lt;p&gt;On the other hand, if there’s a &lt;em&gt;larger org-priority project&lt;/em&gt;, think of how to align your project with it. You might need to make some small changes in the project pitch, but the idea is that you don’t have to build your case from scratch. Instead, you are just piggybacking on an established case by explaining how your project would &lt;em&gt;help&lt;/em&gt; with the goal of completing the larger project. This sort of positioning not only helps reduce the amount of foundational work needed to make people understand the &lt;em&gt;why&lt;/em&gt; but also ensures that your project will continue to receive attention as a side effect of now being associated with the larger project.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#reprioritization&#34;&gt;
    &lt;h2 id=&#34;reprioritization&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Reprioritization&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Prioritization changes all the time. When there’s a top-down change in priority, make sure to ask clarifying questions to understand the reasoning behind the shift. From there, you have to then recalibrate your (team’s) projects to make sure they’re still aligned with the higher-level goals. On some occasions, this could also mean that you would have to recommend priority shifts on ongoing / planned projects depending on the leadership goal change. For example, you&amp;rsquo;re in the core infrastructure team and the company decides to pivot to &amp;ldquo;everything AI&amp;rdquo;. Make sure to understand &amp;ldquo;how AI&amp;rdquo; and potentially shift your prioritization around (like adding better GPU support, direct Ollama support etc.) to fit the leadership goal change.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#goal-setting&#34;&gt;
    &lt;h2 id=&#34;goal-setting&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Goal setting&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Management (and shareholders) love &lt;em&gt;goals&lt;/em&gt;. They paint a picture of predictability and allow for an easy way to evaluate how you (or the team) are doing. The higher level you get (with bigger scope and projects), the goal would be closer to company-level goals. The hierarchy probably goes something like this: (company-level goal) make more money -&amp;gt; sell more ads -&amp;gt; get more users -&amp;gt; keep existing users returning (team-level goal) etc. You don’t have to set the goal on how to make the company more money, but you can set a goal on how to keep existing users returning which, by proxy, would have your impact cascaded up.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#wrap-up&#34;&gt;
    &lt;h2 id=&#34;wrap-up&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Wrap up&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Admittedly, this is something even I sometimes forget or overlook when pitching a project. The key insight is that effective communication isn’t about having the best technical solution but rather, it&amp;rsquo;s about meeting people where they are and speaking their language. When you frame your ideas through your audience’s priorities and align with existing organizational momentum, you’re offering value in return of their support.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>GitHub Action Runner Alternatives</title>
      <link>https://binhong.me/blog/2025-09-02-github-action-runner-alternatives/</link>
      <pubDate>Tue, 02 Sep 2025 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2025-09-02-github-action-runner-alternatives/</guid>
      <description>&lt;p&gt;A few months ago, I wrote about my attempt and reasoning behind &lt;a href=&#34;https://binhong.me/blog/2025-04-25-self-hosted-github-runners/&#34;&gt;managing my own fleet of self-hosted GitHub Action Runners&lt;/a&gt;. I&amp;rsquo;ve since learnt that there are quite a few hosted alternative runners that are both faster and cheaper than GitHub&amp;rsquo;s default runners. This article is just a quick summary of what I&amp;rsquo;ve found.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#linux-runners-comparison&#34;&gt;
    &lt;h2 id=&#34;linux-runners-comparison&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Linux Runners Comparison&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Provider&lt;/th&gt;
          &lt;th&gt;Free Tier&lt;/th&gt;
          &lt;th&gt;Paid Pricing (2vCPU)&lt;/th&gt;
          &lt;th&gt;Performance&lt;/th&gt;
          &lt;th&gt;Special Features&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://www.blacksmith.sh/&#34;&gt;Blacksmith&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;3,000 minutes/month&lt;/td&gt;
          &lt;td&gt;$0.004/min (50% of GitHub)&lt;/td&gt;
          &lt;td&gt;2x faster builds, 4x faster cache&lt;/td&gt;
          &lt;td&gt;Unlimited concurrency, gaming CPUs&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://buildjet.com/&#34;&gt;BuildJet&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;$5 one-time credit&lt;/td&gt;
          &lt;td&gt;$0.004/min (50% of GitHub)&lt;/td&gt;
          &lt;td&gt;2x faster (variable CPU models)&lt;/td&gt;
          &lt;td&gt;64 AMD + 32 ARM concurrent vCPUs, 20GB cache&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://carbonrunner.io/&#34;&gt;CarbonRunner&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;❌ None&lt;/td&gt;
          &lt;td&gt;$0.006/min (25% cheaper than GitHub)&lt;/td&gt;
          &lt;td&gt;Standard performance&lt;/td&gt;
          &lt;td&gt;90% lower CO2 emissions, 42 regions&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://cirrus-runners.app/&#34;&gt;Cirrus Runners&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;❌ None&lt;/td&gt;
          &lt;td&gt;$150/month unlimited&lt;/td&gt;
          &lt;td&gt;2-3x faster performance&lt;/td&gt;
          &lt;td&gt;Fixed pricing, no minute limits, limited concurrency&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;http://depot.dev/&#34;&gt;Depot&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;❌ None&lt;/td&gt;
          &lt;td&gt;$20 for 20,000 minutes, then $0.004/min&lt;/td&gt;
          &lt;td&gt;30% faster, 10x faster cache&lt;/td&gt;
          &lt;td&gt;Docker-focused, per-second billing, unlimited cache&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;GitHub&lt;/td&gt;
          &lt;td&gt;2,000 minutes/month&lt;/td&gt;
          &lt;td&gt;$0.008/min&lt;/td&gt;
          &lt;td&gt;Baseline&lt;/td&gt;
          &lt;td&gt;Official support&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://namespace.so/&#34;&gt;Namespace&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;❌ None&lt;/td&gt;
          &lt;td&gt;$0.003&lt;/td&gt;
          &lt;td&gt;Fastest x64 performance&lt;/td&gt;
          &lt;td&gt;250GB storage, SSH access&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://puzl.cloud/&#34;&gt;puzl.cloud&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;400 vCPU-minutes/month&lt;/td&gt;
          &lt;td&gt;$0.0008/min (€0.000008/vCPU-sec)&lt;/td&gt;
          &lt;td&gt;2x faster, 5x cheaper&lt;/td&gt;
          &lt;td&gt;Real-time per-second billing, 12vCPU per job&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://sprinters.sh/&#34;&gt;Sprinters.sh&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;❌ None&lt;/td&gt;
          &lt;td&gt;Per-job pricing (10x cheaper)&lt;/td&gt;
          &lt;td&gt;Standard performance&lt;/td&gt;
          &lt;td&gt;Self-hosted in AWS, per-job pricing&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://runs-on.com/&#34;&gt;RunsOn&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;Free for non-commercial&lt;/td&gt;
          &lt;td&gt;€300/year + $0.001-0.003/min (AWS costs)&lt;/td&gt;
          &lt;td&gt;30% faster, 90% cheaper&lt;/td&gt;
          &lt;td&gt;Self-hosted in AWS, GPUs, Windows&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://www.ubicloud.com/&#34;&gt;Ubicloud&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;1,250 minutes (~$1 credit)&lt;/td&gt;
          &lt;td&gt;$0.0008/min (10x cheaper)&lt;/td&gt;
          &lt;td&gt;Newer CPUs, good performance&lt;/td&gt;
          &lt;td&gt;Open source, Hetzner hardware&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://www.warpbuild.com/&#34;&gt;WarpBuild&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;❌ None&lt;/td&gt;
          &lt;td&gt;$0.004/min (50% of GitHub)&lt;/td&gt;
          &lt;td&gt;2x faster, 50% cheaper&lt;/td&gt;
          &lt;td&gt;Snapshots, unlimited cache&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#macos-runners-comparison&#34;&gt;
    &lt;h2 id=&#34;macos-runners-comparison&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;macOS Runners Comparison&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Provider&lt;/th&gt;
          &lt;th&gt;Pricing&lt;/th&gt;
          &lt;th&gt;Hardware&lt;/th&gt;
          &lt;th&gt;Performance&lt;/th&gt;
          &lt;th&gt;Key Features&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://cirrus-runners.app/&#34;&gt;Cirrus Runners&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;$150/month unlimited&lt;/td&gt;
          &lt;td&gt;M4 Pro (latest)&lt;/td&gt;
          &lt;td&gt;2x faster performance&lt;/td&gt;
          &lt;td&gt;Fixed pricing, no minute limits, limited concurrency&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;GitHub&lt;/td&gt;
          &lt;td&gt;$0.08/min (10x multiplier)&lt;/td&gt;
          &lt;td&gt;Intel Mac + M2 Pro XL ($0.16/min)&lt;/td&gt;
          &lt;td&gt;Baseline&lt;/td&gt;
          &lt;td&gt;Official support&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://namespace.so/&#34;&gt;Namespace&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;$0.09&lt;/td&gt;
          &lt;td&gt;M4 Pro + M2 Pro&lt;/td&gt;
          &lt;td&gt;Fastest available&lt;/td&gt;
          &lt;td&gt;SSH/VNC access, bleeding-edge images&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;a href=&#34;https://www.warpbuild.com/&#34;&gt;WarpBuild&lt;/a&gt;&lt;/td&gt;
          &lt;td&gt;$0.04/min (50% cheaper)&lt;/td&gt;
          &lt;td&gt;M2 Pro&lt;/td&gt;
          &lt;td&gt;25% faster, 50% cheaper&lt;/td&gt;
          &lt;td&gt;Pay-per-use, macOS 13/14 support&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#widget&#34;&gt;
    &lt;h2 id=&#34;widget&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Widget&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;I don&amp;rsquo;t have a better place to put this but I built a very basic widget that takes into account of the &amp;ldquo;supposed&amp;rdquo; faster speed (leading to less action minutes) with each of their rates so you can find out which platform fits you best based on your usage (with GitHub&amp;rsquo;s Action Runner minutes as baseline).&lt;/p&gt;
&lt;style&gt;
  .calculator-widget {
    font-family: VictorMono;
    margin: 20px auto;
    padding: 20px;
    border: 1px solid var(--default-gray);
    border-radius: 12px;
  }

  #darkModeToggle:checked~div .calculator-widget {
    border: 1px solid var(--dark-gray);
  }

  .inputs {
    display: flex;
    gap: 15px;
    align-items: end;
  }

  .input-group {
    flex: 1;
  }

  .input-group label {
    display: block;
    margin-bottom: 5px;
  }

  .input-group input,
  .input-group select {
    width: 100%;
  }

  .free {
    color: var(--default-links);
  }

  #darkModeToggle:checked~div .free {
    color: var(--dark-links);
  }

  .speed-toggle-switch {
    position: relative;
    display: inline-block;
    width: 50px;
    height: 24px;
  }

  .speed-toggle-switch input {
    opacity: 0;
  }

  .speed-toggle-slider {
    position: absolute;
    cursor: pointer;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: var(--default-gray);
    transition: .4s;
    border-radius: 24px;
  }

  .speed-toggle-slider:before {
    position: absolute;
    content: &#34;&#34;;
    height: 18px;
    width: 18px;
    left: 3px;
    bottom: 3px;
    background-color: var(--default-background);
    transition: .4s;
    border-radius: 50%;
  }

  .speed-toggle-switch input:checked + .speed-toggle-slider {
    background-color: var(--default-active);
  }

  .speed-toggle-switch input:checked + .speed-toggle-slider:before {
    transform: translateX(26px);
  }

  #darkModeToggle:checked~div .speed-toggle-switch input:checked + .speed-toggle-slider {
    background-color: var(--dark-active);
  }

  #darkModeToggle:checked~div .speed-toggle-slider {
    background-color: var(--dark-gray);
  }

  #darkModeToggle:checked~div .speed-toggle-slider:before {
    background-color: var(--dark-background);
  }

  .speed-toggle-label {
    display: flex;
    align-items: center;
    gap: 10px;
    cursor: pointer;
    padding: 0 0 15px 2px;
  }

  @media (max-width: 750px) {
    .inputs {
      flex-direction: column;
    }

    .input-group {
      width: 100%;
    }
  }
&lt;/style&gt;
&lt;div class=&#34;calculator-widget&#34;&gt;
  &lt;div class=&#34;inputs&#34;&gt;
    &lt;div class=&#34;input-group&#34;&gt;
      &lt;label for=&#34;minutes&#34;&gt;GitHub Action Minutes&lt;/label&gt;
      &lt;input type=&#34;number&#34; id=&#34;minutes&#34; value=&#34;1000&#34; min=&#34;0&#34; step=&#34;100&#34;&gt;
    &lt;/div&gt;
    &lt;div class=&#34;input-group&#34;&gt;
      &lt;label for=&#34;platform&#34;&gt;Platform&lt;/label&gt;
      &lt;select id=&#34;platform&#34;&gt;
        &lt;option value=&#34;linux&#34;&gt;Linux (2 vCPU)&lt;/option&gt;
        &lt;option value=&#34;macos&#34;&gt;macOS&lt;/option&gt;
      &lt;/select&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;label for=&#34;ignoreSpeed&#34; class=&#34;speed-toggle-label&#34;&gt;
    &lt;div class=&#34;speed-toggle-switch&#34;&gt;
      &lt;input type=&#34;checkbox&#34; id=&#34;ignoreSpeed&#34; checked&gt;
      &lt;span class=&#34;speed-toggle-slider&#34;&gt;&lt;/span&gt;
    &lt;/div&gt;
    Ignore Speed
  &lt;/label&gt;

  &lt;table class=&#34;results-table&#34; id=&#34;results&#34;&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Provider&lt;/th&gt;
        &lt;th id=&#34;speed-header&#34;&gt;Speed&lt;/th&gt;
        &lt;th&gt;Monthly Cost&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody id=&#34;results-body&#34;&gt;
      
    &lt;/tbody&gt;
  &lt;/table&gt;
&lt;/div&gt;

&lt;script&gt;
  const providers = {
    linux: [
      { name: &#34;Blacksmith&#34;, link: &#34;https://www.blacksmith.sh/&#34;, freeMinutes: 3000, pricePerMinute: 0.004, speedMultiplier: 2.0 },
      { name: &#34;BuildJet&#34;, link: &#34;https://buildjet.com/&#34;, freeMinutes: 0, pricePerMinute: 0.004, speedMultiplier: 2.0 },
      { name: &#34;CarbonRunner&#34;, link: &#34;https://carbonrunner.io/&#34;, freeMinutes: 0, pricePerMinute: 0.006, speedMultiplier: 1.0 },
      { name: &#34;Cirrus Runners&#34;, link: &#34;https://cirrus-runners.app/&#34;, freeMinutes: 0, pricePerMinute: 0, fixedCost: 150, speedMultiplier: 2.5 },
      { name: &#34;Depot&#34;, link: &#34;http://depot.dev/&#34;, freeMinutes: 2000, fixedCost: 20, pricePerMinute: 0.004, speedMultiplier: 1.3 },
      { name: &#34;GitHub&#34;, freeMinutes: 2000, pricePerMinute: 0.008, speedMultiplier: 1.0 },
      { name: &#34;Namespace&#34;, link: &#34;https://namespace.so/&#34;, freeMinutes: 0, pricePerMinute: 0.003, speedMultiplier: 1.8 },
      { name: &#34;puzl.cloud&#34;, link: &#34;https://puzl.cloud/&#34;, freeMinutes: 400, pricePerMinute: 0.0008, speedMultiplier: 2.0 },
      { name: &#34;RunsOn&#34;, link: &#34;https://runs-on.com/&#34;, freeMinutes: 0, pricePerMinute: 0.002, fixedCost: 25, speedMultiplier: 1.3 },
      { name: &#34;Ubicloud&#34;, link: &#34;https://www.ubicloud.com/&#34;, freeMinutes: 1250, pricePerMinute: 0.0008, speedMultiplier: 1.3 },
      { name: &#34;WarpBuild&#34;, link: &#34;https://www.warpbuild.com/&#34;, freeMinutes: 0, pricePerMinute: 0.004, speedMultiplier: 2.0 },
    ],
    macos: [
      { name: &#34;Cirrus Runners&#34;, link: &#34;https://cirrus-runners.app/&#34;, freeMinutes: 0, pricePerMinute: 0, fixedCost: 150, speedMultiplier: 2.0 },
      { name: &#34;GitHub&#34;, freeMinutes: 0, pricePerMinute: 0.08, speedMultiplier: 1.0 },
      { name: &#34;Namespace&#34;, link: &#34;https://namespace.so/&#34;, freeMinutes: 0, pricePerMinute: 0.09, speedMultiplier: 1.8 },
      { name: &#34;WarpBuild&#34;, link: &#34;https://www.warpbuild.com/&#34;, freeMinutes: 0, pricePerMinute: 0.08, speedMultiplier: 1.25 },
    ]
  };

  function calculateCosts() {
    const minutes = parseInt(document.getElementById(&#39;minutes&#39;).value) || 0;
    const platform = document.getElementById(&#39;platform&#39;).value;
    const ignoreSpeed = document.getElementById(&#39;ignoreSpeed&#39;).checked;
    const tbody = document.getElementById(&#39;results-body&#39;);
    const speedHeader = document.getElementById(&#39;speed-header&#39;);

    speedHeader.style.display = ignoreSpeed ? &#39;none&#39; : &#39;&#39;;

    const platformProviders = providers[platform];

    let results = platformProviders.map(provider =&gt; {
      let cost = 0;
      
      const actualMinutes = ignoreSpeed ? minutes : minutes / provider.speedMultiplier;
      
      if (provider.fixedCost &amp;&amp; provider.pricePerMinute === 0) {
        cost = provider.fixedCost;
      } else if (provider.fixedCost) {
        const freeMinutes = provider.freeMinutes || 0;
        cost = provider.fixedCost + (Math.max(0, actualMinutes - freeMinutes) * provider.pricePerMinute);
      } else {
        const freeMinutes = provider.freeMinutes || 0;
        const billableMinutes = Math.max(0, actualMinutes - freeMinutes);
        cost = billableMinutes * provider.pricePerMinute;
      }
      const isFree = cost === 0;

      return { ...provider, cost, isFree };
    });

    results.sort((a, b) =&gt; a.cost - b.cost);

    tbody.innerHTML = results.map(provider =&gt; {
      const costDisplay = provider.isFree ? &#39;FREE&#39; : `${provider.cost.toFixed(2)}`;
      const costClass = provider.isFree ? &#39;free&#39; : &#39;cost&#39;;
      const providerName = provider.link ? `&lt;a href=&#34;${provider.link}&#34; noreferrer noopener&gt;${provider.name}&lt;/a&gt;` : `${provider.name}`;
      const speedColumn = ignoreSpeed ? &#39;&#39; : `&lt;td class=&#34;provider-speed&#34;&gt;${provider.speedMultiplier}&lt;/td&gt;`;

      return `
                    &lt;tr&gt;
                        &lt;td class=&#34;provider-name&#34;&gt;${providerName}&lt;/td&gt;
                        ${speedColumn}
                        &lt;td class=&#34;${costClass}&#34;&gt;${costDisplay}&lt;/td&gt;
                    &lt;/tr&gt;
                `;
    }).join(&#39;&#39;);
  }

  document.getElementById(&#39;minutes&#39;).addEventListener(&#39;input&#39;, calculateCosts);
  document.getElementById(&#39;platform&#39;).addEventListener(&#39;change&#39;, calculateCosts);
  document.getElementById(&#39;ignoreSpeed&#39;).addEventListener(&#39;change&#39;, calculateCosts);

  calculateCosts();
&lt;/script&gt;
&lt;p&gt;&lt;em&gt;Note: Sprinters.sh is priced per job instead of per min (like everyone else) so I didn&amp;rsquo;t include it in the comparison above.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note2: Cirrus Runners limits on concurrency instead of minutes so the experience might vary (depending on usage).&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note3: &amp;ldquo;Speed&amp;rdquo; is loosely defined based on acclaimed performance but not verified in any way, shape, or form.&lt;/em&gt;&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#wrap-up&#34;&gt;
    &lt;h2 id=&#34;wrap-up&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Wrap up&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Obviously this is a rather simplistic view of the cost comparison (as well as the &lt;em&gt;speed&lt;/em&gt; factor being unverified) between different options available in the market. Personally, I opted for Blacksmith for all my linux needs while continue to host my own M4 mac mini at home as the macOS runner (since the electricity cost is still cheaper than any of the above options).&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Hidden Privacy / Security Pitfalls</title>
      <link>https://binhong.me/blog/2025-08-23-hidden-privacy-and-security-pitfalls/</link>
      <pubDate>Sat, 23 Aug 2025 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2025-08-23-hidden-privacy-and-security-pitfalls/</guid>
      <description>&lt;p&gt;Most software engineers think of this as &amp;ldquo;someone else&amp;rsquo;s problem&amp;rdquo; (usually a privacy engineer or security engineer), but realistically, there&amp;rsquo;s only so much stuff (bad code lol) they can catch. Especially if you work in some of the more sensitive areas, it&amp;rsquo;s not realistic to have them do code audits for every change. This means that you as the code author (or reviewer) need to at least pick up some basic instincts about things that can pose risks.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is part of a series &lt;a href=&#34;https://binhong.me/blog/2025-05-04-the-opinionated-engineer/&#34;&gt;(The Opinionated Engineer)&lt;/a&gt; where I share my strong opinions on engineering practices.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt;: I&amp;rsquo;m neither a privacy engineer nor a security engineer (but a software engineer) so take this mostly as a way to raise awareness about considering some of these pitfalls while building instead of as an expert opinion on what to do.&lt;/em&gt;&lt;/p&gt;
        
&lt;a class=&#34;anchor&#34; href=&#34;#timing-attack&#34;&gt;
    &lt;h2 id=&#34;timing-attack&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Timing attack&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Even if you return the same response every time, you might still be leaking data (or mostly inference) based on the timing it took the server to respond. As an example, a typical email + password login system probably looks like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Hash password&lt;/li&gt;
&lt;li&gt;Email lookup&lt;/li&gt;
&lt;li&gt;Compare hash&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now, considering that password hashing can take quite a bit of time and resources, we could rearrange the order to make it more efficient by making use of &amp;ldquo;early returns.&amp;rdquo; Something like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Email lookup&lt;/li&gt;
&lt;li&gt;Hash password&lt;/li&gt;
&lt;li&gt;Compare hash&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;With this new setup, you&amp;rsquo;d only do password hashing when an email already exists in your record, thus saving time and processing power from unnecessary password hashing. However, you&amp;rsquo;re also returning your network request much sooner. This becomes an unintentional leak where a bad actor can use it to tell if an email exists in your database or not by doing some timed response comparison. My favorite solution to this problem (when applicable) is to add a random response delay—not long enough to significantly affect user experience, but just long enough to throw an attacker off.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#scraping&#34;&gt;
    &lt;h2 id=&#34;scraping&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Scraping&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Generally the &lt;em&gt;easy&lt;/em&gt; solution here is to slap a rate limit on it and call it a day. The problem, however, is that you need to realize not all endpoints are created equal. Most evidently, you have an endpoint that returns info about a user when given an ID, while you also have an endpoint that returns info about &lt;strong&gt;a list of users&lt;/strong&gt; when given a list of IDs. Both of these endpoints can&amp;rsquo;t just share the same rate limiting because scraping the latter endpoint is &lt;code&gt;list_length&lt;/code&gt;x more &lt;em&gt;efficient&lt;/em&gt; than the former.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#credential-stuffing&#34;&gt;
    &lt;h2 id=&#34;credential-stuffing&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Credential stuffing&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;This is usually the result of password reuse. While you can &lt;em&gt;technically&lt;/em&gt; do credential stuffing with just randomly enumerated credentials, the cost is unlikely to be worth the outcome for the attacker (unless there are some specific exceptions). Since people reuse passwords and there have been many previous hacks (unfortunately containing plaintext passwords), your perfectly secure authentication system becomes vulnerable not because of your code, but because of breaches elsewhere. That said, these attacks are &lt;em&gt;usually&lt;/em&gt; more obvious, coming from an &lt;em&gt;expected&lt;/em&gt; set of IPs (all from the same location or same VPS provider, etc.).&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#contactpoint-disclosure&#34;&gt;
    &lt;h2 id=&#34;contactpoint-disclosure&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Contactpoint disclosure&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;A little different from the rest of the list, this is more of a &lt;em&gt;type&lt;/em&gt; of disclosure than a &lt;em&gt;method&lt;/em&gt; of disclosure. While many systems are more conservative and opt for the route of never even disclosing if a specific contactpoint (email / phone number) is even associated with the platform, some do allow for it. However, this will require a bit more care in handling such disclosure where we don&amp;rsquo;t want to disclose &lt;strong&gt;other&lt;/strong&gt; contactpoints being connected to a given contactpoint (even through inference). The main reason being that this risks leaking identities behind pseudonyms where people can &lt;em&gt;connect the dots&lt;/em&gt;.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#rate-limiting&#34;&gt;
    &lt;h2 id=&#34;rate-limiting&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Rate limiting&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Rate limiting is both a valuable tool and also a potential risk of exposure. Most evidently, rate limiting helps reduce (and hopefully prevent) scraping and credential stuffing attacks. However, in some systems, rate limits are set on a per-account basis, making it a risk of exposure as you can do targeted attacks with some reasonable correlation to get information out of it.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s an example:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Condition: email A and phone A are both owned by the same account while email B is not.&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;try (and fail) login with email A -&amp;gt; no rate limit&lt;/li&gt;
&lt;li&gt;try (and fail) login with email B -&amp;gt; no rate limit&lt;/li&gt;
&lt;li&gt;try (and fail) login with phone A -&amp;gt; no rate limit&lt;/li&gt;
&lt;li&gt;keep trying email A until it&amp;rsquo;s rate limited&lt;/li&gt;
&lt;li&gt;try (and fail) login with email B -&amp;gt; no rate limit (since different account)&lt;/li&gt;
&lt;li&gt;try (and fail) login with phone A -&amp;gt; &lt;strong&gt;rate limited&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are many reasons (or other situations) where it&amp;rsquo;s not immediately obvious how introducing an account-based rate limit can be a risk.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#phone-recycling&#34;&gt;
    &lt;h2 id=&#34;phone-recycling&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Phone recycling&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Many platforms rely on phone number verification to authenticate your ownership of the account, but phone number ownership isn&amp;rsquo;t exactly &lt;em&gt;immutable&lt;/em&gt;. In fact, phone numbers commonly switch hands in most countries for a variety of reasons (especially as people move across countries). Unfortunately, this is a much trickier problem to tackle due to the wide variety of carriers spread across different countries having different processes and regulations around it.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#email-recycling&#34;&gt;
    &lt;h2 id=&#34;email-recycling&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Email recycling&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;While email recycling isn&amp;rsquo;t as common as phone recycling, it still persists (especially those with custom domains). In fact, many years ago one of the largest email service providers actually allowed anyone to register expired email addresses. This became a major attack vector on Facebook since many people back then set their emails to be shown publicly on their Facebook profiles, while also simultaneously stopping using those same email addresses - leaving them to expire. Thankfully, most email providers have learned of such attack vectors and no longer allow deactivated / deleted email addresses to be re-registered &lt;em&gt;ever&lt;/em&gt;.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#wrap-up&#34;&gt;
    &lt;h2 id=&#34;wrap-up&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Wrap up&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;These pitfalls often emerge from well-intentioned optimizations that look perfectly reasonable in isolation. The key is developing a mental checklist of common attack patterns rather than becoming paranoid about every line of code. Security isn&amp;rsquo;t just about preventing obvious attacks but instead, it&amp;rsquo;s to think through the subtle ways your system behavior can be observed and / or exploited.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Sync git submodules with GitHub Action</title>
      <link>https://binhong.me/blog/2025-08-08-sync-git-submodules-github-action/</link>
      <pubDate>Fri, 08 Aug 2025 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2025-08-08-sync-git-submodules-github-action/</guid>
      <description>&lt;p&gt;I needed to share some object code across multiple repos (to ensure the consistency of the RESTful API JSON serialization and deserialization). So I created a dedicated repository where all the shared code lives, then imported it across other repos as a submodule. In this case, I used a library I made - &lt;a href=&#34;https://wings.sh/&#34;&gt;wings&lt;/a&gt; - but usually you&amp;rsquo;d probably use something like Thrift or Protocol Buffers. Another common use case would be to have a repository hosting shared cross-platform (iOS + Android) low-level code.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a bit of a pain to manually update it across all the different repositories (and check if it causes any regressions) every time I make changes to it. So instead I wrote some tests, set up some GitHub Actions to run them, then have an automated GitHub Action that sends PRs to all the &amp;ldquo;parent&amp;rdquo; repos every time it receives a new commit, which will trigger the underlying CI tests so it&amp;rsquo;d be obvious if it might break anything.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#setup-submodules&#34;&gt;
    &lt;h2 id=&#34;setup-submodules&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Setup submodules&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;First, create the shared git repository. Then in the parent repositories, run &lt;code&gt;git submodule add &amp;lt;path&amp;gt; &amp;lt;git_url&amp;gt;&lt;/code&gt;. Commit these changes and now you should see the directory shown as a ref link in GitHub like &lt;code&gt;{dir_name} @ {commit_hash}&lt;/code&gt;. It&amp;rsquo;s important to note that the folder won&amp;rsquo;t be initialized automatically on clone, but instead you need to either clone with the &lt;code&gt;--recurse-submodules&lt;/code&gt; argument or run &lt;code&gt;git submodule update --init --recursive&lt;/code&gt; after cloning to get the submodules set up properly.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#granting-pats&#34;&gt;
    &lt;h2 id=&#34;granting-pats&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Granting PATs&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;You mostly need 2 PATs here. One for all repositories importing the shared repo to be able to &lt;em&gt;read&lt;/em&gt; both the hosting repo and the shared repo so its GitHub Actions can run &lt;code&gt;git submodule init &amp;amp;&amp;amp; git submodule update&lt;/code&gt; as part of its &amp;ldquo;checkout&amp;rdquo; step. (They could be multiple separate PATs each with specific repo access, but in my case I just use the same PAT that allows read access across all repos in the GitHub organization.) The other one is for the shared repo (to be used later) to clone, branch, commit, and create pull requests on all the repos extending it.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#creating-github-action-for-updates&#34;&gt;
    &lt;h2 id=&#34;creating-github-action-for-updates&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Creating GitHub Action for updates&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Here&amp;rsquo;s something I vibe-coded with Claude for my own project (&lt;a href=&#34;https://globetrotte.com/&#34;&gt;GlobeTrotte&lt;/a&gt;). The main thing that needs updating is the &lt;code&gt;SUBMODULE_PATH&lt;/code&gt; and &lt;code&gt;DEFAULT_CONFIG&lt;/code&gt; section to fit your use case. It is a bit more on the verbose side of things, but it does work as expected. One tricky callout I&amp;rsquo;d say is that the PATs can be a little tricky if you need to both &lt;em&gt;read&lt;/em&gt; from another repo (as you would to initialize the submodule) and &lt;em&gt;write&lt;/em&gt; as you try to push changes to another branch (like &amp;ldquo;deploy&amp;rdquo; or &amp;ldquo;gh-pages&amp;rdquo; branch). You can instead split them into separate actions with proper dependency and use different PATs depending on the need.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#workflow-configuration-and-triggers&#34;&gt;
    &lt;h3 id=&#34;workflow-configuration-and-triggers&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Workflow configuration and triggers&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h3&gt;
&lt;/a&gt;
&lt;p&gt;This sets up the basic workflow structure and defines which repositories should be updated:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;25
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;26
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;27
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;28
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;29
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;30
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;Update Submodules and Create PR&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;on&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;push, pull_request ]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;env&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# Static submodule path used across all configurations&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;SUBMODULE_PATH&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;src/wings&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;c&#34;&gt;# Default configuration - can be overridden by workflow input&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;DEFAULT_CONFIG&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;sd&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;    [
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;      {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;        &amp;#34;target_repo&amp;#34;: &amp;#34;repo-1&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;        &amp;#34;prefix&amp;#34;: &amp;#34;./&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;        &amp;#34;target_branch&amp;#34;: &amp;#34;main&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;        &amp;#34;enabled&amp;#34;: true
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;      },
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;      {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;        &amp;#34;target_repo&amp;#34;: &amp;#34;repo-2&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;        &amp;#34;prefix&amp;#34;: &amp;#34;./&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;        &amp;#34;target_branch&amp;#34;: &amp;#34;main&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;        &amp;#34;enabled&amp;#34;: true
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;      },
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;      {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;        &amp;#34;target_repo&amp;#34;: &amp;#34;repo-3&amp;#34;, 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;        &amp;#34;prefix&amp;#34;: &amp;#34;app/&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;        &amp;#34;target_branch&amp;#34;: &amp;#34;main&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;        &amp;#34;enabled&amp;#34;: true
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;    ]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The &lt;code&gt;DEFAULT_CONFIG&lt;/code&gt; is a JSON array where each object represents a repository that uses your submodule. You can enable/disable repositories individually and specify different target branches per repo.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#setup-job-creating-the-execution-matrix&#34;&gt;
    &lt;h3 id=&#34;setup-job-creating-the-execution-matrix&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Setup job: Creating the execution matrix&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h3&gt;
&lt;/a&gt;
&lt;p&gt;This job processes the configuration and creates a matrix for parallel execution across multiple repositories:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;jobs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;setup&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;runs-on&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;ubuntu-latest&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;outputs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;matrix&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;${{ steps.set-matrix.outputs.matrix }}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;steps&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;Set up configuration matrix&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;set-matrix&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;sd&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          if [ -n &amp;#34;${{ github.event.inputs.config_override }}&amp;#34; ]; then
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            echo &amp;#34;Using override configuration&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            CONFIG=&amp;#39;${{ github.event.inputs.config_override }}&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          else
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            echo &amp;#34;Using default configuration&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            CONFIG=&amp;#39;${{ env.DEFAULT_CONFIG }}&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          fi
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          # Filter only enabled configurations and format for matrix
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          MATRIX=$(echo &amp;#34;$CONFIG&amp;#34; | jq -c &amp;#39;[.[] | select(.enabled == true)]&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          echo &amp;#34;matrix=$MATRIX&amp;#34; &amp;gt;&amp;gt; $GITHUB_OUTPUT
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          echo &amp;#34;Configuration matrix:&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          echo &amp;#34;$MATRIX&amp;#34; | jq .&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This step filters out disabled repositories and creates a matrix that GitHub Actions can use to run the update job in parallel for each target repository.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#main-update-job-repository-checkout-and-setup&#34;&gt;
    &lt;h3 id=&#34;main-update-job-repository-checkout-and-setup&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Main update job: Repository checkout and setup&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h3&gt;
&lt;/a&gt;
&lt;p&gt;The main job runs for each repository in the matrix. First, it checks out the target repository and sets up git configuration:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;25
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;26
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;27
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;28
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;29
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;30
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;31
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;32
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;33
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;34
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;update-submodules&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;needs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;setup&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;runs-on&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;ubuntu-latest&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;${{ fromJson(needs.setup.outputs.matrix)[0] != null }}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;strategy&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;matrix&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;config&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;${{ fromJson(needs.setup.outputs.matrix) }}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;fail-fast&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;steps&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;Display current configuration&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;sd&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          echo &amp;#34;🎯 Target Repository: ${{ matrix.config.target_repo }}&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          echo &amp;#34;📂 Submodule Path: ${{ env.SUBMODULE_PATH }}&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          echo &amp;#34;🌿 Target Branch: ${{ matrix.config.target_branch }}&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          # Construct full repo path
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          FULL_REPO_PATH=&amp;#34;{org-name}/${{ matrix.config.target_repo }}&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          echo &amp;#34;FULL_REPO_PATH=$FULL_REPO_PATH&amp;#34; &amp;gt;&amp;gt; $GITHUB_ENV&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;Checkout external repository&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;uses&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;actions/checkout@v4&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;with&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;repository&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;${{ env.FULL_REPO_PATH }}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;${{ secrets.UPDATE_SUBMODULE }}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;ref&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;${{ matrix.config.target_branch }}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;fetch-depth&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;${{ matrix.config.target_repo }}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;Configure Git&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;sd&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          cd ${{ matrix.config.target_repo }}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          git config user.name &amp;#34;GitHub Actions Bot&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          git config user.email &amp;#34;actions@github.com&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Remember to replace &lt;code&gt;{org-name}&lt;/code&gt; with your actual GitHub organization name.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#submodule-initialization-and-version-checking&#34;&gt;
    &lt;h3 id=&#34;submodule-initialization-and-version-checking&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Submodule initialization and version checking&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h3&gt;
&lt;/a&gt;
&lt;p&gt;This is the core logic that handles submodule authentication, updates, and determines if changes are needed:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;25
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;26
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;27
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;28
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;29
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;30
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;31
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;32
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;33
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;34
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;35
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;36
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;37
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;38
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;39
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;40
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;41
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;42
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;43
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;44
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;Initialize and update submodules&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;sd&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          cd ${{ matrix.config.target_repo }}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          git submodule init
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          # Update submodule URLs to use PAT authentication
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          git config --list | grep &amp;#34;submodule\.&amp;#34; | grep &amp;#34;\.url=&amp;#34; | while read -r line; do
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            key=$(echo &amp;#34;$line&amp;#34; | cut -d&amp;#39;=&amp;#39; -f1)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            url=$(echo &amp;#34;$line&amp;#34; | cut -d&amp;#39;=&amp;#39; -f2-)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            if echo &amp;#34;$url&amp;#34; | grep -q &amp;#34;github.com&amp;#34;; then
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;              if echo &amp;#34;$url&amp;#34; | grep -q &amp;#34;https://&amp;#34;; then
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;                new_url=$(echo &amp;#34;$url&amp;#34; | sed &amp;#39;s|https://github.com/|https://x-access-token:${{ secrets.UPDATE_SUBMODULE }}@github.com/|&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;              else
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;                new_url=$(echo &amp;#34;$url&amp;#34; | sed &amp;#39;s|git@github.com:|https://x-access-token:${{ secrets.UPDATE_SUBMODULE }}@github.com/|&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;              fi
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;              git config &amp;#34;$key&amp;#34; &amp;#34;$new_url&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            fi
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          done
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          # Update submodules
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          git submodule update --init --recursive
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          # Navigate to the specific submodule and get current commit
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          cd &amp;#34;${{ env.SUBMODULE_PATH }}&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          CURRENT_COMMIT=$(git rev-parse HEAD)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          echo &amp;#34;Current submodule commit: $CURRENT_COMMIT&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          # Fetch latest changes from submodule&amp;#39;s remote
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          git fetch origin
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          # Get the latest commit on the default branch
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          LATEST_COMMIT=${{ github.sha }}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          echo &amp;#34;Latest submodule commit: $LATEST_COMMIT&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          # Check if update is needed
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          if [ &amp;#34;$CURRENT_COMMIT&amp;#34; != &amp;#34;$LATEST_COMMIT&amp;#34; ]; then
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            echo &amp;#34;UPDATE_NEEDED=true&amp;#34; &amp;gt;&amp;gt; $GITHUB_ENV
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            echo &amp;#34;CURRENT_COMMIT=$CURRENT_COMMIT&amp;#34; &amp;gt;&amp;gt; $GITHUB_ENV
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            echo &amp;#34;LATEST_COMMIT=$LATEST_COMMIT&amp;#34; &amp;gt;&amp;gt; $GITHUB_ENV
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          else
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            echo &amp;#34;UPDATE_NEEDED=false&amp;#34; &amp;gt;&amp;gt; $GITHUB_ENV
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            echo &amp;#34;✅ Submodule is already up to date&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          fi&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This step converts SSH URLs to HTTPS with PAT authentication, then compares the current submodule commit with the latest commit to determine if an update is needed.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#updating-the-submodule-and-creating-branches&#34;&gt;
    &lt;h3 id=&#34;updating-the-submodule-and-creating-branches&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Updating the submodule and creating branches&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h3&gt;
&lt;/a&gt;
&lt;p&gt;If an update is needed, these steps handle the actual submodule update and branch creation:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;25
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;26
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;27
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;28
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;29
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;30
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;31
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;32
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;33
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;34
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;35
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;36
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;37
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;38
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;39
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;40
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;41
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;42
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;Update submodule to latest version&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;env.UPDATE_NEEDED == &amp;#39;true&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;sd&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          cd ${{ matrix.config.target_repo }}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          cd &amp;#34;${{ env.SUBMODULE_PATH }}&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          # Checkout the latest commit
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          git checkout ${{ github.sha }}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;Print changes&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;env.UPDATE_NEEDED == &amp;#39;true&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;sd&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          cd ${{ matrix.config.target_repo }}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          git diff&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;Create feature branch and commit changes&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;${{ env.UPDATE_NEEDED == &amp;#39;true&amp;#39; &amp;amp;&amp;amp; github.ref == &amp;#39;refs/heads/main&amp;#39; }}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;sd&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          cd ${{ matrix.config.target_repo }}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          git add .
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          # Create a new branch for the update
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          SUBMODULE_NAME=&amp;#34;${{ env.SUBMODULE_PATH }}&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          BRANCH_NAME=&amp;#34;update-${SUBMODULE_NAME}-$(date +%Y%m%d-%H%M%S)&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          echo &amp;#34;BRANCH_NAME=$BRANCH_NAME&amp;#34; &amp;gt;&amp;gt; $GITHUB_ENV
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          git checkout -b $BRANCH_NAME
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          # Create detailed commit message
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          COMMIT_MSG=&amp;#34;Update ${SUBMODULE_NAME} submodule
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          Updated ${{ env.SUBMODULE_PATH }} from ${{ env.CURRENT_COMMIT }} to ${{ env.LATEST_COMMIT }}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          This automated update brings in the latest changes from the submodule.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          Repository: ${{ matrix.config.target_repo }}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          Branch: ${{ matrix.config.target_branch }}&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          git commit -m &amp;#34;$COMMIT_MSG&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          # Push the new branch
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          git push origin $BRANCH_NAME&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This creates a timestamped branch name, commits the submodule update with a detailed message, and pushes the branch to the target repository.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#creating-pull-requests&#34;&gt;
    &lt;h3 id=&#34;creating-pull-requests&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Creating pull requests&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h3&gt;
&lt;/a&gt;
&lt;p&gt;Finally, this step creates a pull request in the target repository with detailed information about the update:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;25
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;26
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;27
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;28
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;29
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;30
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;31
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;32
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;33
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;34
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;35
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;36
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;37
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;38
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;39
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;40
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;41
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;42
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;43
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;44
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;45
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;46
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;47
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;48
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;Create Pull Request&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;${{ env.UPDATE_NEEDED == &amp;#39;true&amp;#39; &amp;amp;&amp;amp; github.ref == &amp;#39;refs/heads/main&amp;#39; }}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;uses&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;actions/github-script@v7&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;with&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;github-token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;${{ secrets.UPDATE_SUBMODULE }}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;working-directory&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;${{ matrix.config.target_repo }}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;script&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;sd&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            const submoduleName = &amp;#39;${{ env.SUBMODULE_PATH }}&amp;#39;;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            const repoOwner = &amp;#39;GlobeTrotte-com&amp;#39;; // Replace with actual owner
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            const repoName = &amp;#39;${{ matrix.config.target_repo }}&amp;#39;;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            const { data: pullRequest } = await github.rest.pulls.create({
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;              owner: repoOwner,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;              repo: repoName,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;              title: `Update ${submoduleName} submodule`,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;              head: process.env.BRANCH_NAME,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;              base: &amp;#39;${{ matrix.config.target_branch }}&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;              body: `## Submodule Update
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;              This PR updates the \`${{ env.SUBMODULE_PATH }}\` submodule to the latest version.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;              ### Changes
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;              - **From Commit:** \`${{ env.CURRENT_COMMIT }}\`
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;              - **To Commit:** \`${{ env.LATEST_COMMIT }}\`
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;              ### Details
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;              This automated update was triggered by a push to the main branch and brings in the latest changes from the submodule repository.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;              Please review the changes in the submodule repository before merging this PR.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;              ---
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;              *This PR was created automatically by GitHub Actions*`,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;              draft: false
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            });
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            console.log(`Pull request created: ${pullRequest.html_url}`);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            // Add labels if desired
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            try {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;              await github.rest.issues.addLabels({
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;                owner: repoOwner,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;                repo: repoName,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;                issue_number: pullRequest.number,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;                labels: [&amp;#39;dependencies&amp;#39;, &amp;#39;submodule-update&amp;#39;, &amp;#39;automated&amp;#39;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;              });
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            } catch (error) {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;              console.log(&amp;#39;Note: Could not add labels (labels may not exist in target repo)&amp;#39;);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            }&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;a class=&#34;anchor&#34; href=&#34;#summary-and-completion&#34;&gt;
    &lt;h3 id=&#34;summary-and-completion&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Summary and completion&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h3&gt;
&lt;/a&gt;
&lt;p&gt;These final steps provide logging and status reporting:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;25
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;26
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;27
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;28
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;Summary&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;sd&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          if [ &amp;#34;${{ env.UPDATE_NEEDED }}&amp;#34; == &amp;#34;true&amp;#34; ]; then
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            echo &amp;#34;✅ Submodule updated and PR created successfully!&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            echo &amp;#34;Repository: ${{ matrix.config.target_repo }}&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            echo &amp;#34;Path: ${{ env.SUBMODULE_PATH }}&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            echo &amp;#34;Branch: ${{ env.BRANCH_NAME }}&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          else
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            echo &amp;#34;ℹ️ Submodule ${{ env.SUBMODULE_PATH }} in ${{ matrix.config.target_repo }} is already up to date.&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          fi&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;final-summary&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;needs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;setup, update-submodules ]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;runs-on&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;ubuntu-latest&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;always()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;steps&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;Workflow Summary&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;sd&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          echo &amp;#34;🎉 Submodule update workflow completed!&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          echo &amp;#34;Processed repositories based on configuration matrix.&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          if [ &amp;#34;${{ needs.update-submodules.result }}&amp;#34; == &amp;#34;success&amp;#34; ]; then
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            echo &amp;#34;✅ All updates completed successfully&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          elif [ &amp;#34;${{ needs.update-submodules.result }}&amp;#34; == &amp;#34;failure&amp;#34; ]; then
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            echo &amp;#34;❌ Some updates failed - check individual job results&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          else
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;            echo &amp;#34;ℹ️ Workflow completed with status: ${{ needs.update-submodules.result }}&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;          fi&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;a class=&#34;anchor&#34; href=&#34;#bonus-auto-accept--merge-action&#34;&gt;
    &lt;h2 id=&#34;bonus-auto-accept--merge-action&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;[Bonus] Auto-accept + merge action&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;I&amp;rsquo;m leaving this as an open recommendation (and homework lol) for you to implement. The assumption here is that the change is generally safe and so it should be auto-accepted + merged into the target repositories &lt;strong&gt;once it passes all the tests&lt;/strong&gt;.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#wrapping-up&#34;&gt;
    &lt;h2 id=&#34;wrapping-up&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Wrapping Up&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;This automated submodule update workflow can save significant time and reduce the risk of human error when managing shared code across multiple repositories. While the initial setup requires some configuration and PAT management, the long-term benefits of having consistent, up-to-date shared code across your entire project ecosystem are substantial. The workflow provides clear visibility into what&amp;rsquo;s being updated through detailed pull requests and commit messages, while still maintaining the safety net of requiring CI tests to pass before any changes are merged. Consider implementing the bonus auto-merge functionality only after you&amp;rsquo;ve gained confidence in your test coverage and are comfortable with the automation&amp;rsquo;s reliability.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Firefighting Heroes</title>
      <link>https://binhong.me/blog/2025-08-01-firefighting-heroes/</link>
      <pubDate>Fri, 01 Aug 2025 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2025-08-01-firefighting-heroes/</guid>
      <description>&lt;p&gt;We love celebrating heroes in general. So when a hero shows up to save the day, it&amp;rsquo;s only natural that we make sure to recognize their contribution. While it might feel counterintuitive, we should strive to not need heroes entirely instead of hoping that next time when tragedy strikes, a hero will turn up once again. This is not to diminish the value of heroes. They still play a very important role in firefighting and are doing the right thing in an urgent situation. However, &lt;em&gt;in a perfect world&lt;/em&gt;, they shouldn&amp;rsquo;t be necessary at all and we should at least aspire to work towards building a system with that level of reliability.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is part of a series &lt;a href=&#34;https://binhong.me/blog/2025-05-04-the-opinionated-engineer/&#34;&gt;(The Opinionated Engineer)&lt;/a&gt; where I share my strong opinions on engineering practices.&lt;/em&gt;&lt;/p&gt;
        
&lt;a class=&#34;anchor&#34; href=&#34;#bian-que-and-his-brothers&#34;&gt;
    &lt;h2 id=&#34;bian-que-and-his-brothers&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Bian Que and his brothers&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;If you&amp;rsquo;ve never heard of the story about &lt;em&gt;Bian Que and his brothers&lt;/em&gt;, here&amp;rsquo;s a &lt;a href=&#34;https://www.meta.ai/@binhonglee/prompt/iS2XuvsxNDq&#34;&gt;Meta AI summary of it&lt;/a&gt; with all the relevant details:&lt;/p&gt;
&lt;blockquote class=&#34;long_quote&#34;&gt;
    &lt;p&gt;Bian Que was a renowned Chinese physician who lived during the Warring States period (475-221 BCE). According to legend, Bian Que and his two older brothers were all skilled in medicine.&lt;/p&gt;
&lt;p&gt;The story goes that the eldest brother treated illnesses before they became apparent, the second brother treated illnesses when they were still subtle, and Bian Que treated illnesses when they were severe and obvious.&lt;/p&gt;
&lt;p&gt;When people saw Bian Que&amp;rsquo;s dramatic cures, they credited him with great skill. However, Bian Que said that his brothers were actually more skilled because they treated problems before they became serious.&lt;/p&gt;
&lt;p&gt;This story highlights the importance of preventive medicine and early intervention in Chinese medical philosophy.&lt;/p&gt;

&lt;/blockquote&gt;
&lt;p&gt;I mention this because one time when I was complaining about all the fires happening in our org and how I keep getting looped into them to help put them out, my then tech lead cited this story to me (which is a little offensive 😡 but she&amp;rsquo;s not wrong lol). I think in general, this is an accurate depiction of the &lt;strong&gt;misplaced prioritization and recognition&lt;/strong&gt; where we consistently reward &amp;ldquo;heroes&amp;rdquo; solving issues rather than those who worked to prevent fires from happening to begin with simply due to visibility and measurability.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#automate-whenever-possible&#34;&gt;
    &lt;h2 id=&#34;automate-whenever-possible&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Automate whenever possible&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;I remember once reading a Site Reliability Engineer write (verbatim), &lt;em&gt;&amp;ldquo;my job is to automate myself out of my own job&amp;rdquo;&lt;/em&gt; which I thought was an interesting way to look at things but also an obvious starting point. To do away with the reliance on firefighting heroes, automate the things they do whenever possible. As an example, when something breaks and you find an automated test for that specific breakage, have a system that automatically runs bisect against said test to find the blame commit causing the breakage. Taking it a step further, the system can run all these automated tests every time before release and if anything is broken, bisect to find the blame commit and revert it, then run everything again until there&amp;rsquo;s a good stable version that&amp;rsquo;s ready for release.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#prevention--cure&#34;&gt;
    &lt;h2 id=&#34;prevention--cure&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Prevention &amp;gt; Cure&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;As the idiom says, &lt;em&gt;&amp;ldquo;prevention is better than cure&amp;rdquo;&lt;/em&gt;. In a previous piece about &lt;a href=&#34;https://binhong.me/blog/2025-05-30-no-blame-sev-culture/#process-over-people&#34;&gt;Process over People&lt;/a&gt;, I&amp;rsquo;ve talked about the focus on building process as a preventative measure. This is a similar point where we should ideally focus more on &lt;strong&gt;preventing&lt;/strong&gt; outages from even happening instead of just putting them out &lt;strong&gt;after&lt;/strong&gt; they&amp;rsquo;re already on fire. The hard part however is - yet again - that the visibility of good prevention work is never as prominent as good firefighting work. There is a two-fold solution to this (where ideally both need to happen). Firstly, if you (or you know someone) are doing important prevention work, ensure people are aware of their work. Secondly, leadership needs to prominently acknowledge the value of such work and reflect it as such in &lt;em&gt;whichever-way-they-usually-do-recognition&lt;/em&gt; and in performance reviews.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#accountability-on-reliability&#34;&gt;
    &lt;h2 id=&#34;accountability-on-reliability&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Accountability on reliability&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;One of the common pitfalls here is that, once something is shipped, the responsibility to keep it running falls on the team (whoever is on-call at the time of incident) instead of the individual who shipped an &lt;em&gt;incomplete&lt;/em&gt; product. This is something I&amp;rsquo;ve previously touched on &lt;a href=&#34;https://binhong.me/blog/2025-05-30-no-blame-sev-culture/#no-blame--no-responsibility&#34;&gt;here&lt;/a&gt;. If someone is taking credit for the successful launch (and all its underlying &lt;strong&gt;impact&lt;/strong&gt;), they should also be held accountable if the launch causes reliability issues (be it during or shortly after the launch). As to how &lt;em&gt;accountability&lt;/em&gt; looks, it can range anywhere from taking ownership of resolving the issue long term, to being penalized in their performance review cycle for an incomplete launch. There&amp;rsquo;s quite a bit of nuance here depending on factors like the scale of outages, any visible warning signs, how preventable the issue was, etc.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#wrap-up&#34;&gt;
    &lt;h2 id=&#34;wrap-up&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Wrap up&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;This is as much (if not more) of a culture problem as it is a technical problem. Building reliable systems isn&amp;rsquo;t just about writing better code or implementing more monitoring. It requires fundamentally shifting how we think about and reward engineering work. We need to move away from the hero worship that celebrates last-minute saves and instead build cultures that value the unglamorous work of prevention, automation, and long-term system health. Heroes will always be needed in truly exceptional circumstances, but if your organization consistently relies on them to keep the lights on, it&amp;rsquo;s a sign that your systems, processes, and culture need serious attention. The goal isn&amp;rsquo;t to eliminate heroes entirely but to build systems so robust that heroic interventions become rare exceptions rather than regular occurrences.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Major Incident Runbook</title>
      <link>https://binhong.me/blog/2025-07-25-major-incident-runbook/</link>
      <pubDate>Fri, 25 Jul 2025 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2025-07-25-major-incident-runbook/</guid>
      <description>&lt;p&gt;I wrote a similar version of this internally at Meta a few years ago for my org after finding myself in the middle of a few SEV1s in a row &amp;ndash; and being consulted / asked for support in other similar situations. I thought this might be something useful to share (as a public version) as well. This won&amp;rsquo;t be perfectly fitting for all use cases, but having a runbook works as an anchor in the midst of chaos, helping to get you unstuck from &amp;ldquo;what&amp;rsquo;s next?&amp;rdquo;. Admittedly, this is an incomplete runbook that serves more as a template for your team or company to complete with more specific tooling guides (using &lt;em&gt;which&lt;/em&gt; tool to achieve &lt;em&gt;what&lt;/em&gt;, etc.).&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is part of a series &lt;a href=&#34;https://binhong.me/blog/2025-05-04-the-opinionated-engineer/&#34;&gt;(The Opinionated Engineer)&lt;/a&gt; where I share my strong opinions on engineering practices.&lt;/em&gt;&lt;/p&gt;
        
&lt;a class=&#34;anchor&#34; href=&#34;#stop-the-bleed&#34;&gt;
    &lt;h2 id=&#34;stop-the-bleed&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Stop the Bleed!&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;The highest priority is to stop the bleeding immediately. &lt;em&gt;Flip a killswitch, roll back changes, apply server-side fixes, apply client-side fixes&lt;/em&gt; - &lt;strong&gt;in that order&lt;/strong&gt;. A killswitch generally propagates faster, thus is preferred over code changes, but even then, prioritize rolling back changes instead of forward fixing. Forward fixing adds more unknown factors into the mix (because that&amp;rsquo;s more new code which could now cause new / different problems); rolling back, on the other hand, is more predictable. Server-side fixes over client-side fixes should be pretty obvious since you can guarantee the server version (as the service provider), but you can&amp;rsquo;t always force a client to update (native apps), or there could be some caching involved (web).&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#multiple-workstreams&#34;&gt;
    &lt;h2 id=&#34;multiple-workstreams&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Multiple Workstreams&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;During a major incident, there are generally multiple things that can or need to happen in parallel. Break them down into clear workstreams and delegate a domain expert to run each of them. If more things are discovered down the line (or the situation changes), switch up the workstream and get different people (domain experts) involved to run different things. Since you&amp;rsquo;re dealing with an incident (which are usually time sensitive), &lt;strong&gt;never hesitate to call people&lt;/strong&gt;. This needs to be said a lot because people constantly hesitate about false positives or getting on others&amp;rsquo; bad side for inaccurately calling them up. But you won&amp;rsquo;t know what you don&amp;rsquo;t know without getting the domain expert to show up and verify what you&amp;rsquo;re seeing.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#chat-management&#34;&gt;
    &lt;h2 id=&#34;chat-management&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Chat Management&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Alongside having separate workstreams, you should also set up separate chat threads for each workstream to keep the &lt;em&gt;main chat&lt;/em&gt; low on noise. That said, make sure to announce the establishment and / or major milestones of each new workstream clearly within the main chat so everyone relevant is correctly included. You might be in a lot of different chats and things will be chaotic, so you will need to take extra care in ensuring that the right people are in the right chat to allow them to get their different tasks going. Everyone should still be in the main chat, but most discussion should happen in the workstream chat.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#sync-meetings&#34;&gt;
    &lt;h2 id=&#34;sync-meetings&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Sync Meetings&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;There are generally 2 types of sync meetings. First is the &amp;ldquo;everything just happened so we started a meeting to info dump and get everyone up to speed.&amp;rdquo; This meeting will always be chaotic with a lot of people joining to figure out what&amp;rsquo;s happening and what to do next. (There might also be people who only joined out of curiosity, but as long as they aren&amp;rsquo;t interrupting, they are the least of your problems - unless Zoom is not scaling lol.) Set out a clear goal, a set of tasks, and an owner for each of those tasks. Finally, set a follow-up date and time (usually in a few hours) for everyone to regroup with new findings and progress to determine next steps.&lt;/p&gt;
&lt;p&gt;The second type is the follow-up and / or recurring meeting. This will feel more like your regular team standup meeting except with a lot more urgency. It&amp;rsquo;s important to note that if there&amp;rsquo;s any major breakthrough, people shouldn&amp;rsquo;t wait until the next planned meeting time to report (because, well, this is an outage lol) but should instead share it with everyone immediately. This goes back to the previous point about chat management where you (as the incident manager) will need to monitor each workstream chat to catch something like this. After resolving the core issue, there might still be &lt;em&gt;important&lt;/em&gt; cleanup work needed to be handled with a similar level of urgency to prevent the same outage from happening again in a very short time. In these long-running incident cleanups, you might opt to have daily (or even twice-a-day) sync meetings until the cleanup is complete.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#wrap-up&#34;&gt;
    &lt;h2 id=&#34;wrap-up&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Wrap up&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;As a whole, you will largely be more like a PM / TPM instead of a regular engineer when handling a lot of this &lt;em&gt;alignment&lt;/em&gt; work. If you think someone else is better suited to handle this (another more senior engineer or your manager, etc.), ask for help while you focus on the thing that you do best (likely as the subject matter expert on investigation or remediation). Remember that incident management is a skill that improves with practice - don&amp;rsquo;t expect to be perfect on your first few incidents, even experienced engineers can feel overwhelmed when everything is on fire. Don&amp;rsquo;t forget to acknowledge the team&amp;rsquo;s efforts since major incidents are stressful for everyone involved, and use this runbook as a starting point while adapting it based on your team&amp;rsquo;s specific needs and continuously improving your processes with each incident.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Understanding the Value of Dev Tools</title>
      <link>https://binhong.me/blog/2025-07-18-understanding-value-of-dev-tools/</link>
      <pubDate>Fri, 18 Jul 2025 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2025-07-18-understanding-value-of-dev-tools/</guid>
      <description>&lt;p&gt;I&amp;rsquo;m largely approaching this from the perspective of building an internal dev tool (since this is mostly where my personal experiences are coming from), especially if you&amp;rsquo;re someone who wants to build an internal tool but is having trouble framing it in a way where its value can be understood by the decision makers. I&amp;rsquo;m also excluding situations where you &amp;ldquo;have to&amp;rdquo; build certain tools to track certain information in order to comply with a legal requirement, since I don&amp;rsquo;t think that would be controversial to comply with. I&amp;rsquo;d imagine the framework for using an external dev tool, while similar, also poses extra complexity on paying vs building.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is part of a series &lt;a href=&#34;https://binhong.me/blog/2025-05-04-the-opinionated-engineer/&#34;&gt;(The Opinionated Engineer)&lt;/a&gt; where I share my strong opinions on engineering practices.&lt;/em&gt;&lt;/p&gt;
        
&lt;a class=&#34;anchor&#34; href=&#34;#cost-of-engineers&#34;&gt;
    &lt;h2 id=&#34;cost-of-engineers&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Cost of engineers&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Generally, the simplest way would be to calculate how much time is saved for an employee, multiplied by the cost of hiring the employee. This directly shows how much money the organization is saving by just having this tool around. Pretty straightforward, not a lot of controversy.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#value-generated&#34;&gt;
    &lt;h2 id=&#34;value-generated&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Value generated&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;In organizations where engineering is considered a profit center, and not a cost center, it might make more sense to calculate it with the value generated by the employee against the time saved. That said, this is usually much harder to measure compared to the above, thus more uncommon unless the tooling itself is a direct value generator.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#urgency-disaster-recovery&#34;&gt;
    &lt;h2 id=&#34;urgency-disaster-recovery&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Urgency (Disaster Recovery)&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;In a disaster recovery situation, the time is significantly more valuable than the average &amp;ldquo;engineering hours&amp;rdquo; since it&amp;rsquo;s directly affecting the bottom line of company profit or other top-line metrics (or even consumer trust of the product). Admittedly I&amp;rsquo;ve never figured out the perfect framing around this, but I&amp;rsquo;d reckon phrasing it as &amp;ldquo;used to resolve x, y, z incidents&amp;rdquo; (and if available, attach the severity level for each of the incidents to it) would suffice in clearly articulating this.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#employee-satisfaction&#34;&gt;
    &lt;h2 id=&#34;employee-satisfaction&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Employee satisfaction&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;This is one of the more subjective measurements. While you can attempt to quantify it through things like survey responses, it&amp;rsquo;s important to keep in mind that the improvement will automatically become the new baseline. For example, an 8/10 last year will become a 5/10 this year, so you need to improve it &lt;em&gt;even more&lt;/em&gt; to get that same 8/10 again this year. On some occasions, additional improvement will hit the point of diminishing returns and have very little investment value. It&amp;rsquo;s important to take notice and begin pivoting effort into better sustainability.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#one-off-use-case&#34;&gt;
    &lt;h2 id=&#34;one-off-use-case&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;One-off use case&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Depending on the level of effort, one-off use cases can be a bit of a dangerous misdirection / mismanagement of valuable resources (time). It&amp;rsquo;s sometimes hard to notice, but if it takes you longer to build the tool than to just do the work without it, or the tool provides very minimal value compared to the amount of work needed to build (and maintain) the tool, building them makes no sense. Generally, this is usually a rare problem and only exists for top-down asks because leadership might not understand the day-to-day workflow for impacted employees. Instead, we generally see the opposite where even the most valuable / impactful tools take a lot of work to convince leadership that they&amp;rsquo;re worth the investment.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#not-invented-here-syndrome&#34;&gt;
    &lt;h2 id=&#34;not-invented-here-syndrome&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Not-Invented-Here Syndrome&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Another common trap in building internal tools is the Not-Invented-Here Syndrome. This is especially true for mission-critical tools (alert paging, traffic monitoring, authentication etc.) where it would make more sense (for most companies / orgs) both financially and expertise-wise to &lt;em&gt;outsource&lt;/em&gt; them to dedicated product experts in the market, making &amp;ldquo;buying&amp;rdquo; a better option than &amp;ldquo;building&amp;rdquo;. &amp;ldquo;Buying&amp;rdquo; here doesn&amp;rsquo;t necessarily mean monetary purchase either. For example, if your team begins scaling your code base and needs a large-scale cross-stack build tool, it would make more sense to adopt something like &lt;a href=&#34;https://bazel.build/&#34;&gt;Bazel&lt;/a&gt; or &lt;a href=&#34;https://buck.build/&#34;&gt;Buck&lt;/a&gt; instead of building your own new build tool from scratch.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#wrap-up&#34;&gt;
    &lt;h2 id=&#34;wrap-up&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Wrap up&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Understanding the value of dev tools requires matching your measurement approach to what resonates with your organization&amp;rsquo;s priorities. The goal isn&amp;rsquo;t just to justify building tools, but to build the right ones which can sometimes be as simple as buying an existing solution or accepting a manual process.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Internal Tooling Ideas</title>
      <link>https://binhong.me/blog/2025-07-11-internal-tooling-ideas/</link>
      <pubDate>Fri, 11 Jul 2025 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2025-07-11-internal-tooling-ideas/</guid>
      <description>&lt;p&gt;For years, I built and maintained the only logged-out accessible dev tool set / platform at Meta. That earned me some reputation (in a certain circle) of being &amp;ldquo;the idea guy on internal tools&amp;rdquo;. Whenever I&amp;rsquo;m asked about how I keep coming up with good ideas for valuable tools to build, my go-to answer has been &amp;ldquo;I build tools when I get annoyed while doing my job&amp;rdquo;. I&amp;rsquo;ll try to expand that into more details below.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is part of a series &lt;a href=&#34;https://binhong.me/blog/2025-05-04-the-opinionated-engineer/&#34;&gt;(The Opinionated Engineer)&lt;/a&gt; where I share my strong opinions on engineering practices.&lt;/em&gt;&lt;/p&gt;
        
&lt;a class=&#34;anchor&#34; href=&#34;#repetitive-typing&#34;&gt;
    &lt;h2 id=&#34;repetitive-typing&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Repetitive Typing&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;The biggest value of having logged-out accessible dev tools is that you no longer have to log in to access dev tools. Login, while generally not too big of a friction for one-off situations, can be very bothersome if you have to type your credentials over and over again (for dev purposes) as you login (update config) → logout (test) → login (update config) → logout (test). If bypassing or removing the typing aspect is not possible, consider if it can be replaced with multiple choice button selection or just a single copy-paste friendly input.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#unnecessary-waiting--processing&#34;&gt;
    &lt;h2 id=&#34;unnecessary-waiting--processing&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Unnecessary Waiting / Processing&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;There are generally 2 types of waiting problems. The first one is where the wait takes a really long time and can be done asynchronously. Either move the wait into an async manner and integrate a messaging system to notify the author when it&amp;rsquo;s completed, or pre-process them based on known patterns. For example, companies with large native app (iOS / Android) code bases have long build times. You can integrate build tools like &lt;a href=&#34;https://bazel.build/remote/caching&#34;&gt;Bazel with remote caching&lt;/a&gt; so clients don&amp;rsquo;t have to rebuild everything from scratch. Since an engineer will likely want to pull from remote &lt;code&gt;HEAD&lt;/code&gt; (or &lt;code&gt;stable&lt;/code&gt;) daily, you can have a cron job that pulls and builds daily in the morning before the engineer starts working, thus turning their wait block time into an &lt;em&gt;invisible&lt;/em&gt; operation done automagically while they are having their breakfast or morning coffee. The second type is where the wait is one part of the process and can&amp;rsquo;t be (deterministically) pre-processed ahead of time / asynchronously. In this case, consider if bypassing is an option (e.g., skips in &lt;code&gt;dev&lt;/code&gt; but runs in &lt;code&gt;prod&lt;/code&gt;). If not, you can also try making it non-blocking (move it off the main thread, batch processing, backfill etc.) as the user proceeds through the process. If all else fails, you can always turn that part of the process into an async step with an integration to a messaging system notifying the author once it&amp;rsquo;s ready to proceed to the next step.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#device-config--manual-hard-code&#34;&gt;
    &lt;h2 id=&#34;device-config--manual-hard-code&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Device Config &amp;gt; Manual Hard-code&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;In many of these cases, you can probably do manual hard-coding to get the intended behavior. However, doing so increases the risk of accidentally committing such hard-coded changes into prod (if not caught during code review). On top of that, when working on a complex code base, the engineer might not be familiar with the code pointer for where the hard-code needs to go. For instance, if you want to bypass the rate-limit of your feature but the rate-limit logic is owned by a separate team in an unfamiliar code base. As an added bonus, this also allows non-technical members of your team (designers, PMs etc.) to do product audits, dogfooding, and in-depth testing independently without needing dedicated engineering support.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#non-deterministic-behaviors-ml-override&#34;&gt;
    &lt;h2 id=&#34;non-deterministic-behaviors-ml-override&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Non-deterministic Behaviors (ML override)&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;This is somewhat similar to the manual hardcode situation mentioned above where you might want to override an ML decision to manually test out different decision combinations by the model. ML models (and AI) are relatively non-deterministic by design, but when building / testing a product, you want to make sure that you covered all the different possible scenarios. Having an override to switch between different potential responses allows for better coverage on both manual and automated testing, ensuring that your change behaves as intended.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#chore&#34;&gt;
    &lt;h2 id=&#34;chore&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Chore&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;This is more about employee satisfaction when working on a repetitive task may be boring or frustrating. Having tools to automate such work allows them to explore more interesting / challenging work, thus improving employee satisfaction, even if it doesn&amp;rsquo;t necessarily save a significant amount of time. The best &amp;ldquo;realistic&amp;rdquo; way to measure this would be a rating system where you count the number of employees using it and ask for their feedback (both positive and negative), then compile to show the value it provides.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#wrap-up&#34;&gt;
    &lt;h2 id=&#34;wrap-up&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Wrap up&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Building internal tools is about removing friction from your team&amp;rsquo;s daily workflow. The best tools emerge from genuine pain points you experience while doing your job. Start small and focus on the annoyances that happen most frequently. The key is to stay observant of your own frustrations and act on them. Every great internal tool started with someone saying &amp;ldquo;there has to be a better way to do this&amp;rdquo;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Early takes on vibe-coding</title>
      <link>https://binhong.me/blog/2025-07-03-early-takes-on-vibe-coding/</link>
      <pubDate>Thu, 03 Jul 2025 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2025-07-03-early-takes-on-vibe-coding/</guid>
      <description>&lt;p&gt;I keep hearing about vibe-coding and I&amp;rsquo;ve always written the majority of code myself. While at Meta, I got a chance to try out CodeCompose. It worked really well as an autocomplete but when it tried to do anything more than 5 lines at a time, it would - on many occasions - commit bugs that aren&amp;rsquo;t immediately obvious at first sight. Generally, I&amp;rsquo;ve caught them by looking at the generated code and wondering &amp;ldquo;huh this isn&amp;rsquo;t how I&amp;rsquo;d do this, why?&amp;rdquo;. That said, it definitely helped me code and ship faster especially on mundane tasks. Vibe-coding though, seems like taking it to a whole new level (using even less supervision and care on the code being committed).&lt;/p&gt;
        
&lt;a class=&#34;anchor&#34; href=&#34;#perfect-for-small-isolated-problems&#34;&gt;
    &lt;h2 id=&#34;perfect-for-small-isolated-problems&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Perfect for small, isolated problems&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;I started my attempt by making Claude code out a GitHub Action workflow file. I have a submodule setup (where a repo is shared and imported across multiple other repos) and wanted to have an automated way to tell how its changes will affect code on other repos while also creating PRs to keep them updated. Seems like a perfectly fine isolated problem to try this out on. I did run out of tokens a few times (being on a free plan) so I had to get creative but it largely worked. I&amp;rsquo;d say it behaved like a normal engineer writing a first version (which isn&amp;rsquo;t perfect) but can understand and work its way through debugging and resolving the issue slowly when given clear information on what went wrong.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#not-for-complex-changes-in-an-intern-size-project&#34;&gt;
    &lt;h2 id=&#34;not-for-complex-changes-in-an-intern-size-project&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Not for complex changes in an &lt;em&gt;intern-size&lt;/em&gt; project&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;&lt;em&gt;Note: Using the phrase &amp;ldquo;intern-size&amp;rdquo; here because back then, there was a weird rumor that interns were expected to ship 10k LoC as part of their internship to get return offers in FAANG lol. I don&amp;rsquo;t think it was ever true but definitely a standard people worked towards.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Now that I&amp;rsquo;ve got it working on an isolated problem, I wanted to see how it might handle a complex change in a pre-existing project. I have an Android app codebase (for &lt;a href=&#34;https://globetrotte.com&#34;&gt;GlobeTrotte&lt;/a&gt;) with around 8k+ LoC so I decided to try it on there (using SWE-1 from Windsurf). This is the instruction I provided (admittedly a complex one):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;add new navhost to edittripactivity and make each of edit day and edit place a separate screen instead (so it push-and-pop for each small edit)&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;PS: &lt;code&gt;edittripactivity&lt;/code&gt; is a file name (technically &lt;code&gt;EditTripAcitivity.kt&lt;/code&gt; but I think the LLM understood it), &lt;code&gt;navhost&lt;/code&gt; is a concept of &lt;a href=&#34;https://developer.android.com/develop/ui/compose/navigation#create-navhost&#34;&gt;how screen navigation works in Jetpack Compose&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The LLM took 20+ minutes before running out of time which required me to make a &lt;code&gt;continue&lt;/code&gt; call not just once but twice before telling me it was done. &lt;em&gt;It&amp;rsquo;s all chaos from here on out.&lt;/em&gt; It tells me that there are a bunch of errors so it tries to write more code (?) leading to more errors, so more code, then more errors etc. At some point, I mentioned that there were 88 errors and it figured to try compiling and reading the compiler error (instead of looking for them itself) but that barely cut down the number of errors. &lt;strong&gt;I just kept telling it that there were more errors and it just kept trying to code itself out of the mess by adding more code and thus more errors.&lt;/strong&gt; I eventually gave up and ran &lt;code&gt;git checkout .&lt;/code&gt; to clean everything up.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#losing-track-of-signatures&#34;&gt;
    &lt;h2 id=&#34;losing-track-of-signatures&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Losing track of signatures&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;At some point, it started making up stuff that either existed with a different name, or something that it thought should exist but didn&amp;rsquo;t (or it forgot to add the implementation for it, I can&amp;rsquo;t tell). The first example is that it keeps calling &lt;code&gt;PlaceItem()&lt;/code&gt; even though there&amp;rsquo;s no object with that name (and all the &lt;code&gt;please fix error&lt;/code&gt; prompts never saw it touching them). There is however, an object called &lt;code&gt;Place()&lt;/code&gt; which I&amp;rsquo;m assuming is what it was referring to. The second example is where it called &lt;code&gt;updateDay(delete = true)&lt;/code&gt; despite the fact that &lt;code&gt;updateDay()&lt;/code&gt; has a bunch of other required params while it also doesn&amp;rsquo;t have &lt;code&gt;delete&lt;/code&gt; as a param. I can only assume that it just inferred the functionality of the function without actually understanding if it worked as intended.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#ask-clarifying-questions&#34;&gt;
    &lt;h2 id=&#34;ask-clarifying-questions&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Ask clarifying questions&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;The prompt I provided is a bit vague to be honest. It&amp;rsquo;s asking to make a UX change without actually providing any design example but rather just describing it with words as if the other person would easily understand it. The LLM went to work immediately with that prompt without asking for more clarifying questions like how the screens get triggered, how the layout should work, how the UI should look etc. I think if LLMs can learn to ask clarifying questions, it can be invaluable for situations like this where the ask might be a little too vague to work off of.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#phenomenal-auto-complete-machine&#34;&gt;
    &lt;h2 id=&#34;phenomenal-auto-complete-machine&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Phenomenal auto-complete machine&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;I&amp;rsquo;d be remissed if I didn&amp;rsquo;t mention the auto-complete capabilities of AI coding assistants. In short, they are consistently phenomenal especially when it comes to boilerplate code needing minor tweaks here and there. The AI would make the necessary tweaks automatically making it a breeze when going through the more mind-numbing part of the code base. This is a consistent experience both when I was at Meta (using CodeCompose) and now using Windsurf for my personal project.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#is-it-a-mid-level-engineer-yet&#34;&gt;
    &lt;h2 id=&#34;is-it-a-mid-level-engineer-yet&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Is it a mid-level engineer yet?&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Short answer, no. Long answer, it depends. In terms of raw coding ability in an isolated environment, I think it&amp;rsquo;s meeting the mid-level engineer mark just fine (maybe even better due to its breadth generally uncommon among &amp;ldquo;humans&amp;rdquo; lol) but it&amp;rsquo;s the &lt;em&gt;everything else&lt;/em&gt; part that&amp;rsquo;s an issue. For starters, I expect a mid-level engineer to ask for help instead of mindlessly trying to commit code (or send out PRs) over and over again that isn&amp;rsquo;t compiling. I also don&amp;rsquo;t (usually) have to nudge them that their code isn&amp;rsquo;t compiling or failing tests. They can see it themselves and would go work on debugging and fixing them proactively. This is on top of all the issues mentioned above when working in a &lt;em&gt;not-even-that-large&lt;/em&gt; of a codebase.&lt;/p&gt;
&lt;p&gt;For now though, it seems like it&amp;rsquo;s still not good enough to take over even just the coding part of my job so I guess I&amp;rsquo;m going back to implementing the new &lt;code&gt;navhost&lt;/code&gt; for my Android app by myself.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Experiment Review Process</title>
      <link>https://binhong.me/blog/2025-06-27-experiment-review-process/</link>
      <pubDate>Fri, 27 Jun 2025 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2025-06-27-experiment-review-process/</guid>
      <description>&lt;p&gt;Mature growth teams would organize a centralized experiment review meeting as a way to share learnings to a wider audience, consult for feedback / next step recommendations, while also holding engineers accountable for the changes they are attempting to ship. The review sessions should be open to anyone to sign up (presenting their experiments) or to participate in general. However, key decision makers (like senior growth engineers, product managers, designers, data scientists) should be required to attend so decisions to ship / not ship / iterate will be made with everyone&amp;rsquo;s concerns addressed.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is part of a series &lt;a href=&#34;https://binhong.me/blog/2025-05-04-the-opinionated-engineer/&#34;&gt;(The Opinionated Engineer)&lt;/a&gt; where I share my strong opinions on engineering practices.&lt;/em&gt;&lt;/p&gt;
        
&lt;a class=&#34;anchor&#34; href=&#34;#understanding-the-metric-shifts&#34;&gt;
    &lt;h2 id=&#34;understanding-the-metric-shifts&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Understanding the metric shifts&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;When presenting (and reviewing) an experiment, not only do we look at metric shifts but we need to also make sense of them. Understanding them helps inform making better future changes and iterations while ensuring that this change is working as intended instead of some potential regressions on metric blind spots. This usually means that metric analysis for experiments would go beyond just top-line goal / guardrail metrics but also onto regular impression / click loggings to make sure any underlying funnel shifts make sense.&lt;/p&gt;
&lt;p&gt;As an example, when you add stories to the top of your app, you&amp;rsquo;d see an increase in stories traffic. Make sure to also verify that users are actually clicking on the newly added stories component. At the same time, you might be sacrificing screen time on other parts of your app (especially the feature that used to occupy that screen real estate) so you should also see some drop on that end.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#atomic-changes&#34;&gt;
    &lt;h2 id=&#34;atomic-changes&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Atomic changes&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;This is a bit of an extension from the previous point. It&amp;rsquo;s not uncommon for someone inexperienced to test an overly complex change at one go instead of breaking it into a list of smaller incremental changes. In order to understand which change moved which metric, you need to properly isolate your changes into individual groups (or mix-and-match them with multiple test group setups). This allows you to not only understand what works and what doesn&amp;rsquo;t, but also how each of these different changes might have affected one another. If someone brings in an experiment that&amp;rsquo;s overly complex, you should feel comfortable asking &amp;ldquo;why can&amp;rsquo;t it be smaller (or be run with more test groups)?&amp;rdquo;.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#conflict-of-interest&#34;&gt;
    &lt;h2 id=&#34;conflict-of-interest&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Conflict of interest&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Last week in &lt;a href=&#34;https://binhong.me/blog/2025-06-20-growth-engineer/&#34;&gt;Growth Engineer&lt;/a&gt; we talked a little bit about how sometimes engineers would fall into the trap of looking solely at metrics, over-optimizing them. This is generally good for the engineers themselves (being able to boast larger numbers on their launch) but bad for the business (since they are unlikely to be sustainable long term and will probably regress over time). The role of an experiment review process is to ensure that underlying risks or concerns like this get called out and taken into consideration for ship decisions. In the next few paragraphs, we will explore a few examples of such conflict of interest.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#p-hacking&#34;&gt;
    &lt;h2 id=&#34;p-hacking&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;p hacking&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Usually intentional but sometimes oblivious to the underlying logic, an engineer might participate in p hacking that makes the experiment look better than it really is. One of the most obvious examples would be when someone picks a weirdly specific date range to pull their metrics instead of the more logical (like 1 / 2 week average) standard range. While most experimentation tools would do the statistical analysis and show you results with 95% / 99% / 99.9% confidence levels, there could still be variance (especially on the absolute value derived from its average). On a large change (or accumulation of a lot of smaller changes), this can amount to a rather significant amount.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#long-term-holdout&#34;&gt;
    &lt;h2 id=&#34;long-term-holdout&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Long term holdout&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;On some occasions, the effect of a change (especially negative effects) can only be observed over a longer period of time thus making it important to set up a long-term holdout to better understand the longer-term impact of the change. This generally requires some level of product sense and experience to notice thus we would heavily rely on experienced members in the org to call this out. One common example would be &amp;ldquo;user intent&amp;rdquo; (which was previously discussed more in-depth &lt;a href=&#34;https://binhong.me/blog/2025-06-20-growth-engineer/#user-intent&#34;&gt;here&lt;/a&gt;) where long-term behavior of users will better reflect a user&amp;rsquo;s intent as they adopt new habits towards the product change. The other important part of this (often overlooked) process is to follow up on how the long-term holdout performed. It&amp;rsquo;s not uncommon for teams to launch with a holdout then completely forget about the existence of a holdout (especially if the team or product space ended up going through reorgs) leading to a group of poor users still stuck with the sub-optimal experience.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#wrap-up&#34;&gt;
    &lt;h2 id=&#34;wrap-up&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Wrap up&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;A strong experiment review process acts as both a learning forum and a quality gate, ensuring experiments are properly analyzed and potential conflicts of interest are surfaced before they impact users. The goal isn&amp;rsquo;t to slow down shipping, but to ship smarter by using the collective experience in the room to catch blind spots that individuals might miss.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Growth Engineer</title>
      <link>https://binhong.me/blog/2025-06-20-growth-engineer/</link>
      <pubDate>Fri, 20 Jun 2025 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2025-06-20-growth-engineer/</guid>
      <description>&lt;p&gt;While I&amp;rsquo;ve shipped a lot of growth wins (literally the first line on my resume), I&amp;rsquo;m actually very far from a prototypical growth engineer. That said, in this piece, I want to explore a bit more into what it&amp;rsquo;s like being a growth engineer and what makes you good at being one. Growth engineers are generally 1 -&amp;gt; 100 experts instead of 0 -&amp;gt; 1. They fine-tune every little detail by running a lot of experiments with marginal changes to understand the user problem and drive growth impact (&lt;em&gt;line goes up&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is part of a series &lt;a href=&#34;https://binhong.me/blog/2025-05-04-the-opinionated-engineer/&#34;&gt;(The Opinionated Engineer)&lt;/a&gt; where I share my strong opinions on engineering practices.&lt;/em&gt;&lt;/p&gt;
        
&lt;a class=&#34;anchor&#34; href=&#34;#ab-test-everything&#34;&gt;
    &lt;h2 id=&#34;ab-test-everything&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;A/B Test Everything&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;When I say &lt;em&gt;everything&lt;/em&gt;, I mean &lt;strong&gt;everything&lt;/strong&gt;. Every button change, content change, margin change, slap an experiment on it. You need to be numbers obsessed and understand how anything moves the topline metric (the metric you / your team cares the most about). In that same sense, you might want to run experiments in chunks of small changes instead of a big chunk to better understand how each small change affects the user behavior. This would later help you in better understanding what is a good growth lever vs what isn&amp;rsquo;t. Alternatively (depending on the type of changes), you can also have multiple variants with different mixes to achieve something similar.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;For more on A/B testing, check out &lt;a href=&#34;https://binhong.me/blog/2025-06-06-a-b-testing/&#34;&gt;this other post I wrote a few weeks ago here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#understanding-scale&#34;&gt;
    &lt;h2 id=&#34;understanding-scale&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Understanding Scale&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Scale decides necessity and value. If you have no scale, then there&amp;rsquo;s nothing at the top of your funnel to begin with, which makes optimizing the rest of the funnel a common &amp;ldquo;preemptive optimization&amp;rdquo; behavior. Don&amp;rsquo;t add complexity in places you don&amp;rsquo;t need just because &amp;ldquo;it&amp;rsquo;ll be useful in the future&amp;rdquo;. You can build them in the future when needed, then actually measure the value of such a feature to validate your hypothesis (more on this later about A/B testing).&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#identifying-opportunities&#34;&gt;
    &lt;h2 id=&#34;identifying-opportunities&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Identifying opportunities&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;I wrote &lt;a href=&#34;https://binhong.me/blog/2025-06-13-product-growth-opportunities/&#34;&gt;a separate complementary piece on this a few weeks ago here&lt;/a&gt; mainly because it ended up being so long that it probably deserves to be its own separate thing. This will be a core part of your job and in my opinion the biggest separation factor for a &lt;em&gt;&amp;ldquo;pure bred&amp;rdquo;&lt;/em&gt; (lol) growth engineer. Many engineers get stuck or move away from growth over time (especially around IC5 -&amp;gt; IC6 level) as they realize that they can no longer come up with new ideas and directions to continue growing the product sustainably.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#cost-analysis&#34;&gt;
    &lt;h2 id=&#34;cost-analysis&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Cost analysis&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;On some occasions, there will be cost tradeoffs. Most commonly, by spending more money you can get more users (promotions, SMS cost, etc.) which makes it very important to define and understand how much spending is reasonable to earn how many users (cost per user). From there, you should only target to ship projects where it shows that the &lt;em&gt;cost per user&lt;/em&gt; is lower than the given guideline. Important to note that &lt;em&gt;cost&lt;/em&gt; here isn&amp;rsquo;t always directly money, but also valuable real estate (prominent position in the app) or attention (push notification, email, messages) which some could also have a hard-cap (on top of the aforementioned &lt;em&gt;cost per user&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://binhong.me/blog/img/growth.jpg&#34; target=&#34;_blank&#34;&gt;
    
    &lt;img src=&#34;https://binhong.me/blog/2025-06-20-growth-engineer//blog/img/growth.jpg&#34;&gt;
    
&lt;/a&gt;
&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#user-intent&#34;&gt;
    &lt;h2 id=&#34;user-intent&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;User intent&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;blockquote&gt;
&lt;p&gt;You can&amp;rsquo;t force someone to love you.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When someone (or a team) is metric chasing, the actual user (and their intent) can sometimes be neglected. It&amp;rsquo;s always important to keep them in mind and practice some empathy to better understand what the user actually wants and build around that. Even if you were able to &lt;em&gt;trick&lt;/em&gt; users into something they do not intend to do, the long-term effect of it is likely unsustainable.&lt;/p&gt;
&lt;p&gt;I had a personal counter-example here where I once fixed a 3rd party login issue (after login success, instead of redirecting users back to the 3rd party, we would send them to news feed) resulting in a 2M MAU loss from our test. However, it made sense only because we count every person who sees feed as MAU even when (in this case) they did not intend to do so. We ran a separate long-term experiment and saw that the actual loss was significantly lower than that (like maybe only 5% of the original number) because these people were really just trying to login to a 3rd party app (Spotify, Tinder etc.) instead of wanting to browse news feed.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#failed-tests&#34;&gt;
    &lt;h2 id=&#34;failed-tests&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Failed tests&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;When you take a bet and it didn&amp;rsquo;t materialize, your instinct might be to lay low and quietly do away with the project entirely. That would be a massive mistake. Instead, you should focus on what learnings or takeaways you have that can be shared with everyone else &lt;em&gt;especially for someone who might want to try this again in the future&lt;/em&gt;. Generally, there are some key factors on failure root-cause. Make sure to clearly articulate them as you announce your experiment result for the purpose of having clear future reference. If / when someone were to try this again, they can look back at your attempt and see if the landscape had changed enough (based on your learnings) that it might warrant a new attempt.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#long-term-impact&#34;&gt;
    &lt;h2 id=&#34;long-term-impact&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Long term impact&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;On one hand, everyone hated the idea of a long-term holdout (where a certain group of users will just have missing features for an extended period of time); on the other hand, you need a way to measure long-term impact of your changes. That said, due to lack of commonality in this practice, the accuracy of such measurement may not be as high as regular shorter-term tests.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Long term impact is a myth - some E6+ Growth Engineer&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The timing makes things worse. By the time you see results from a long-term experiment, you&amp;rsquo;re usually in a different performance review cycle than when you built it. This makes it nearly impossible to properly reward thoughtful long-term thinking or penalize short-sighted decisions. What should be a performance measurement tool becomes just a way to look back and say &amp;lsquo;oh, that&amp;rsquo;s interesting&amp;rsquo; after it&amp;rsquo;s too late to matter.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#wrap-up&#34;&gt;
    &lt;h2 id=&#34;wrap-up&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Wrap up&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Growth engineering is fundamentally about disciplined curiosity. You&amp;rsquo;re constantly asking &amp;ldquo;what if we change this?&amp;rdquo; and then actually finding out through experiments rather than debates. It&amp;rsquo;s the difference between having opinions about user behavior and having data about user behavior. Always remember that growth isn&amp;rsquo;t &lt;em&gt;just&lt;/em&gt; about running A/B tests. It&amp;rsquo;s important to also understand how / why users behave a certain way. The challenges are often less &lt;em&gt;technically complex&lt;/em&gt; but more about measurement accuracy, experiment design, and building user-friendly interfaces that even your grandma can easily understand.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Product Growth Opportunities</title>
      <link>https://binhong.me/blog/2025-06-13-product-growth-opportunities/</link>
      <pubDate>Fri, 13 Jun 2025 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2025-06-13-product-growth-opportunities/</guid>
      <description>&lt;p&gt;It&amp;rsquo;s never easy to come up with new ideas that help with growth, but identifying the &lt;em&gt;problem&lt;/em&gt; makes it easier. You&amp;rsquo;ll notice that for the most part in this piece, I&amp;rsquo;ll talk about &lt;em&gt;&amp;ldquo;where&amp;rdquo;&lt;/em&gt; the opportunities are instead of &lt;em&gt;&amp;ldquo;what&amp;rdquo;&lt;/em&gt; because that&amp;rsquo;s usually very domain specific and highly depends on the type of problem you ended up needing to solve.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is part of a series &lt;a href=&#34;https://binhong.me/blog/2025-05-04-the-opinionated-engineer/&#34;&gt;(The Opinionated Engineer)&lt;/a&gt; where I share my strong opinions on engineering practices.&lt;/em&gt;&lt;/p&gt;
        
&lt;a class=&#34;anchor&#34; href=&#34;#funnel-analysis&#34;&gt;
    &lt;h2 id=&#34;funnel-analysis&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Funnel analysis&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;The first thing you learn about identifying opportunities in growth is funnel analysis. Build a funnel logging to understand the user journey and go through it to see where you have the biggest drop. From there, you can take a deeper dive into the flow to understand &lt;strong&gt;why&lt;/strong&gt; it is the way it is and see if it can be improved (or if there&amp;rsquo;s any bugs to fix). Similarly, you can go through bug reports to find patterns on how users are being stuck on a specific part of your flow that might have contributed to the funnel losses.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#product-experience&#34;&gt;
    &lt;h2 id=&#34;product-experience&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Product Experience&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Alternatively, you can go straight to the bug reports to see if there are any obvious blocking issues. From there, you can test the fix with an experiment and eventually ship them. Even when it doesn&amp;rsquo;t yield material impact, the changes are generally safe to ship (unless it caused unexpected regressions) since they are largely considered &amp;ldquo;bug fixes&amp;rdquo;. Aside from that, there are other common issues like app performance, screen load time that have a diminishing return curve in user impact.&lt;/p&gt;
&lt;p&gt;My favorite example here was when Uber was working on an iOS rewrite, while everyone was debating about how cellular download limit matters (or not), &lt;a href=&#34;https://x.com/StanTwinB/status/1336929240516710400&#34;&gt;a data scientist pulled together an experiment to show its material impact&lt;/a&gt; (full story &lt;a href=&#34;https://threadreaderapp.com/thread/1336890442768547845.html&#34;&gt;here&lt;/a&gt; - which is one of my favorite software war stories). Similarly, your team should design and run experiments to understand how certain product experiences can have material user impact (instead of just theorizing over them).&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#weve-tried-that-before&#34;&gt;
    &lt;h2 id=&#34;weve-tried-that-before&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;&amp;ldquo;We&amp;rsquo;ve tried that before&amp;rdquo;&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Often times, new people will join and suggest &lt;em&gt;old&lt;/em&gt; ideas as new. When that happens, it&amp;rsquo;s easy to dismiss the idea by citing that something similar has been tried previously and didn&amp;rsquo;t yield the expected result. The hard (and valuable) thing here, however, would be to understand why it failed previously and if anything has materially changed since then that might now allow this to be a viable idea to be re-attempted. You might still fail, but you will likely learn something new instead of learning the same old lesson again (which would be a waste of everyone&amp;rsquo;s time and resources).&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#studying-your-competitors&#34;&gt;
    &lt;h2 id=&#34;studying-your-competitors&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Studying your competitors&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Sometimes you might find a specific step that has problems (where you&amp;rsquo;re losing a lot more conversion than anywhere else) but you can&amp;rsquo;t figure out how to fix it. Studying how your competition does it can help you see if there&amp;rsquo;s a better way to do that. This doesn&amp;rsquo;t mean their solution would perfectly fit your problem, but it might be worth testing to see if that solution is similarly applicable for you. Alternatively, they might also be struggling with the same issue and what you&amp;rsquo;re both doing is (at least for now) the best solution that anyone has thought of so far. I listed this a lot lower intentionally because it&amp;rsquo;s generally not been &lt;em&gt;that&lt;/em&gt; valuable of an option compared to the other ways of identifying new opportunities above.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#survivorship-bias&#34;&gt;
    &lt;h2 id=&#34;survivorship-bias&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Survivorship Bias&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;I wrote this section title with the &amp;ldquo;WWII plane with red dots&amp;rdquo; image in mind. In case you aren&amp;rsquo;t familiar with it, &lt;a href=&#34;https://claude.ai/share/7d06cfc9-95e7-4474-aae4-b56a6c6d3c99&#34;&gt;here&amp;rsquo;s Claude describing it&lt;/a&gt;. In a similar sense, it can be very easy to fall into the trap of focusing on the demand of your existing users instead of building for new users you have yet to (and want to) acquire. Not to say that existing user feedback is unimportant (which I&amp;rsquo;ve literally just said the opposite previously), but rather that it&amp;rsquo;s important to understand and distinguish which feedback helps keep your existing users happy while which feedback is preventing more users from adopting your product.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#wrap-up&#34;&gt;
    &lt;h2 id=&#34;wrap-up&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Wrap up&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Finding growth opportunities isn&amp;rsquo;t about hunting for silver bullets, but more about understanding where your product is bleeding users and why. Don&amp;rsquo;t let growth pursuits distract from building something people actually want. The best growth &lt;em&gt;&amp;ldquo;hack&amp;rdquo;&lt;/em&gt; is still making a good product that gets people what they want.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>A/B Testing</title>
      <link>https://binhong.me/blog/2025-06-06-a-b-testing/</link>
      <pubDate>Fri, 06 Jun 2025 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2025-06-06-a-b-testing/</guid>
      <description>&lt;p&gt;This is a basic introduction on how to run a good A/B test. A/B testing is a method where your user pool is segmented into multiple groups, allowing you to test different product interactions and understand how these changes affect user behavior. For any metric / data driven team, A/B testing serves as a critical tool in measuring success.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is part of a series &lt;a href=&#34;https://binhong.me/blog/2025-05-04-the-opinionated-engineer/&#34;&gt;(The Opinionated Engineer)&lt;/a&gt; where I share my strong opinions on engineering practices.&lt;/em&gt;&lt;/p&gt;
        
&lt;a class=&#34;anchor&#34; href=&#34;#tooling&#34;&gt;
    &lt;h2 id=&#34;tooling&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Tooling&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;If your employer does not already have a working tool for experimentation, I&amp;rsquo;d recommend adopting one of the existing tools / platforms (like &lt;a href=&#34;https://statsig.com/&#34;&gt;Statsig&lt;/a&gt;, &lt;a href=&#34;https://www.growthbook.io/&#34;&gt;GrowthBook&lt;/a&gt;, &lt;a href=&#34;https://launchdarkly.com/&#34;&gt;LaunchDarkly&lt;/a&gt; etc.) specifically built with this in mind. On a basic level, they should provide a toggle that splits users into 2 (or more) groups for testing purposes. Their downstream metrics (after first exposure) will then be used to measure if / how these changes might have affected the users. While it&amp;rsquo;s &lt;em&gt;possible&lt;/em&gt; to build your own A/B testing tool in-house, the build and maintenance cost is likely not worth it compared to just adopting ready-made ones.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#goal--guardrail-metrics&#34;&gt;
    &lt;h2 id=&#34;goal--guardrail-metrics&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Goal + Guardrail metrics&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;When a measure becomes a target, it ceases to be a good measure&amp;rdquo; - Goodhart&amp;rsquo;s Law&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Goal metrics serve as a target, the specific thing you want to change or improve. While I largely agree with Goodhart&amp;rsquo;s Law, I also believe that a sufficiently comprehensive &lt;strong&gt;set&lt;/strong&gt; of metrics will help mitigate the downside (or at least minimize it) thus making it still &lt;em&gt;relatively&lt;/em&gt; &amp;ldquo;a good measure&amp;rdquo;. This is where guardrail metrics come in to ensure we aren&amp;rsquo;t just &amp;ldquo;sacrificing x to boost y&amp;rdquo; especially in an unsustainable manner.&lt;/p&gt;
&lt;p&gt;As an example, if you want to increase usage on your app, you can provide generous discounts (or even pay users to do so) but that&amp;rsquo;s obviously very unsustainable so you should set a clear budget on how much you can spend on these promotions and / or cost per new active user acquired you allow for such a project to ship. Here your goal metric would be &amp;ldquo;new active users acquired&amp;rdquo; while your guardrail would be cost (lost revenue). They need to both look good from the experiment before you decide on shipping the product change.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#size-and-duration&#34;&gt;
    &lt;h2 id=&#34;size-and-duration&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Size and Duration&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Generally, size and duration are the 2 main levers to help get your experiment sufficient exposure that allows you to make (statistically) informed decisions. If you don&amp;rsquo;t have a large user base then many of your tests would need a large test group with long test duration to show any statistically significant movement. This unfortunately means that your new feature release cycle will be much slower as your experiments will be queued. In this scenario, you will need to smartly pair up different features that complement one another into the same experiment.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#holdout&#34;&gt;
    &lt;h2 id=&#34;holdout&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Holdout&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;In order to accurately measure your team&amp;rsquo;s impact over a certain period of time, you can create a team-wide holdout that keeps a small group of users without the new enhancements. From here, you can see the value of all your team&amp;rsquo;s projects over that period of time (quarter / half) collectively. When you run experiments, you are only testing the impact of specific changes while here you are testing them collectively. Some changes conflict with each other while others complement each other. Ideally you catch them early and run variations of different combinations but you don&amp;rsquo;t always catch them all so having a team wide holdout helps with tracking that.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#novelty-effect&#34;&gt;
    &lt;h2 id=&#34;novelty-effect&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Novelty effect&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;When a new restaurant first opens in a popular area, it will be filled with people lining up to try it out. These people will then attract more people who also decide to check out the restaurant out of curiosity. However, the crowd will slowly wind down over time as it loses its &lt;em&gt;novelty&lt;/em&gt; status. If it&amp;rsquo;s any good, it would still remain popular but unlikely to be as popular as it first opened. This is called novelty effect. When you add a new button to the screen, the user&amp;rsquo;s likelihood to click on it increases due to their curiosity on what it does. You don&amp;rsquo;t want to measure that. Instead, you want to measure the actual traffic (and effect) of it &lt;em&gt;after&lt;/em&gt; the novelty wears down. Just like how a good restaurant will still be popular long after its initial launch, you want your feature to be valuable / impactful even long after it&amp;rsquo;s been launched and users clearly understand what it does.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#security&#34;&gt;
    &lt;h2 id=&#34;security&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Security&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;When making a security related change (fix or enhancement), a lot of these concepts become a bit trickier especially for critical issues. Mainly because bad actors can figure out that you are doing an A/B test based on their account ID / device ID / IP address and do targeted exploits on the unprotected test group. This makes it rather a futile effort to understand whatever topline metric movement you want to study of this change on regular benign users but instead, becomes more like opening a new window for exploits.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#wrap-up&#34;&gt;
    &lt;h2 id=&#34;wrap-up&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Wrap up&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Running good A/B tests isn&amp;rsquo;t just about having the right tools—it&amp;rsquo;s about building the discipline to ask the right questions and measure what actually matters. The fundamentals covered here will prevent most common pitfalls that lead to misleading results or shipping changes that hurt your product. &lt;strong&gt;A/B testing is a means to an end.&lt;/strong&gt; The best growth teams quickly kill bad ideas and double down on what&amp;rsquo;s working. Start simple, get comfortable with the process, and don&amp;rsquo;t let perfect be the enemy of good—a simple, well-run test is infinitely better than a complex experiment that sits in planning forever.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>No Blame SEV (Incident) Culture</title>
      <link>https://binhong.me/blog/2025-05-30-no-blame-sev-culture/</link>
      <pubDate>Fri, 30 May 2025 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2025-05-30-no-blame-sev-culture/</guid>
      <description>&lt;p&gt;Every time there&amp;rsquo;s a major outage at Meta, the first question I get from friends and family is usually &lt;em&gt;&amp;ldquo;did they fire the person who caused it?&amp;rdquo;&lt;/em&gt; which is where I have to explain this concept of &lt;strong&gt;No Blame SEV Culture&lt;/strong&gt;. Especially for an outage so big that a significant number of users are affected, the &lt;em&gt;individual&lt;/em&gt; causing it likely does not have ill intent and there are likely multiple different processes and systems that failed along the way to get us here in the first place.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is part of a series &lt;a href=&#34;https://binhong.me/blog/2025-05-04-the-opinionated-engineer/&#34;&gt;(The Opinionated Engineer)&lt;/a&gt; where I share my strong opinions on engineering practices.&lt;/em&gt;&lt;/p&gt;
        
&lt;a class=&#34;anchor&#34; href=&#34;#process-over-people&#34;&gt;
    &lt;h2 id=&#34;process-over-people&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Process over People&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;When something goes wrong (especially something &lt;em&gt;really catastrophic&lt;/em&gt;), it&amp;rsquo;s usually a combination of both process and people problems. The difference here is that process is more deterministic compared to people. People have off-days, get tired, make mistakes etc. so it&amp;rsquo;s important to have a process (or automated systems) in place to prevent that. This can mean anything from adding more test coverage, lint rules against bad code patterns, and / or more alerts. It is however important to note that they need to &lt;strong&gt;maintain a certain level of quality bar&lt;/strong&gt;. As mentioned in &lt;a href=&#34;https://binhong.me/blog/2025-05-04-push-fearlessly-with-automated-testing/#broken-tests&#34;&gt;the previous article&lt;/a&gt;, flaky / broken tests are tech debt, same goes for noisy lint rules and alerts. Too many noisy lint rules and alerts would lead to engineers disregarding them or adopting a &amp;ldquo;wait-and-see&amp;rdquo; mentality which is not ideal in preventing future outages.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://binhong.me/blog/img/sev_review_trifecta.jpeg&#34; target=&#34;_blank&#34;&gt;
    
    &lt;img src=&#34;https://binhong.me/blog/2025-05-30-no-blame-sev-culture//blog/img/sev_review_trifecta.jpeg&#34;&gt;
    
&lt;/a&gt;
&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#expensive-lesson&#34;&gt;
    &lt;h2 id=&#34;expensive-lesson&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Expensive Lesson&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;One of the more interesting quotes I&amp;rsquo;ve read repeatedly (both within and outside of Meta) about people who caused outages is that they just learned an expensive lesson through that specific outage. Firing them (or letting them go) would mean that your company just paid that expensive price of such a lesson for an employee without actually benefiting from it. This employee will then bring this lesson with them to their next employer who would then benefit from such experience.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#fear&#34;&gt;
    &lt;h2 id=&#34;fear&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Fear&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;One of the more significant downsides of &lt;em&gt;blame&lt;/em&gt;, is that you now instill fear in making any sort of production changes (even calculated ones). Instead, it&amp;rsquo;s important to keep in mind that as your product / infra grows, so should your process. Having strong fear in taking any responsibility for even attempting to improve or make fundamental changes breeds complacency. This can be fine in certain organizations and products (like government software, health tech etc.) where there&amp;rsquo;s almost no tolerance for any sort of outages. That said, this is where you should have good chaos engineering and fail-safe practices to ensure the resiliency of your system.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#no-blame--no-responsibility&#34;&gt;
    &lt;h2 id=&#34;no-blame--no-responsibility&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;No Blame ≠ No Responsibility&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;This is a bit of an exception or outlier effect (and likely the most controversial part of this whole piece). Usually when you cause a really major outage (or multiple for that matter), it&amp;rsquo;s really not your fault (or shouldn&amp;rsquo;t be). But sometimes, smaller outages are understandably less &amp;ldquo;well protected&amp;rdquo; because we expect people to still &lt;em&gt;care&lt;/em&gt; about the things they work on. If you &lt;strong&gt;continuously&lt;/strong&gt; cause outages due to &lt;strong&gt;recklessness&lt;/strong&gt; (&amp;ldquo;lack of &lt;em&gt;care&lt;/em&gt;&amp;rdquo;) especially within a short period of time, you should still be held accountable for it. It&amp;rsquo;s especially common when someone chases the topline metrics movement against a tight timeline (end of a performance review cycle). This does not mean that people should be finger-pointing during the incident review, as before, that should be used to focus on what could&amp;rsquo;ve been better instead. However, it should be brought up separately as part of the performance conversation. It&amp;rsquo;s important to note that this is a scenario where a &lt;em&gt;quantitative change leads to a qualitative change&lt;/em&gt; since an increase in quantity (of incidents) leads to a change in narrative thus should not be used to penalize those who&amp;rsquo;ve only caused one (or maybe two) incidents in a given period of time.&lt;/p&gt;
&lt;p&gt;Aside from that, if there isn&amp;rsquo;t any runbook or recovery plan prepared ahead of time (especially for predictable issues - &lt;em&gt;*subjective*&lt;/em&gt;), it demonstrates a lack of good planning and foresight into the feature. This is - frankly - a lack of competence and the project owner should take responsibility for the rather &lt;em&gt;incomplete&lt;/em&gt; launch. However, the reality is that many individuals would launch buggy projects, claim credit for all the good it brings, while oncalls (spread across the team) pay for the lack of implementation quality. This is especially true when they immediately switch teams after project launches and no longer have to maintain or deal with the aftermath of their uninspiring launch.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#wrap-up&#34;&gt;
    &lt;h2 id=&#34;wrap-up&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Wrap up&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;No blame culture means that you aren&amp;rsquo;t fully responsible just because you accidentally touched the house of cards causing it to collapse. Instead, we need better protection around it - like building a fence around it, using LEGO blocks instead of cards, etc - to make sure it doesn&amp;rsquo;t break down easily again after someone accidentally touches it, or just prevent people from accidentally touching it altogether. This means we hold those who are responsible for ensuring the protection accountable instead of those who inevitably discovered the problem. It&amp;rsquo;s like how we don&amp;rsquo;t blame white hat hackers for discovering an exploit; we pay them bounties as a way to thank them for discovering them. We should thank those who found holes in our system&amp;rsquo;s reliability instead of penalizing them for finding it.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Code Review Culture</title>
      <link>https://binhong.me/blog/2025-05-23-code-review-culture/</link>
      <pubDate>Fri, 23 May 2025 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2025-05-23-code-review-culture/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Ask a programmer to review 10 lines of code, he&amp;rsquo;ll find 10 issues. Ask him to do 500 lines and he&amp;rsquo;ll say it looks good. - &lt;a href=&#34;https://x.com/girayozil/status/306836785739210752&#34;&gt;@girayozil&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Code review is a really subjective thing where each team or even individual runs things very differently. However, a bad code review process can lead to bad code smells and unnecessary tech debt (just ask all the vibe coders out there 🫣). I will try my best to share my &lt;em&gt;rather opinionated&lt;/em&gt; takes while explaining the reasoning behind each of them.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is part of a series &lt;a href=&#34;https://binhong.me/blog/2025-05-04-the-opinionated-engineer/&#34;&gt;(The Opinionated Engineer)&lt;/a&gt; where I share my strong opinions on engineering practices.&lt;/em&gt;&lt;/p&gt;
        
&lt;a class=&#34;anchor&#34; href=&#34;#overly-prescriptive-formatters&#34;&gt;
    &lt;h2 id=&#34;overly-prescriptive-formatters&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Overly prescriptive formatters&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;I&amp;rsquo;m highly supportive of having a preset of an overly prescriptive formatter. It doesn&amp;rsquo;t matter if it&amp;rsquo;s spaces vs tabs, 2 spaces vs 4 spaces, curly braces on the same line or newline; as long as you have a formatter that keeps it consistent. This cuts down on all the unnecessary time reviewing and fixing code formatting while still being easy to read and scan through. Make them as strict as possible, ideally 100% reproducible where every line of code only has one way it can be formatted. Again, it doesn&amp;rsquo;t matter which style you pick but just pick something and stick with it, it&amp;rsquo;s all about consistency here.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#bias-against-newbies&#34;&gt;
    &lt;h2 id=&#34;bias-against-newbies&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Bias against newbies&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;There was some research that finds that when someone is new to the stack / team / industry, reviewers tend to be stricter and more nitpicky against their code changes relative to a long-timer or a more senior engineer. Personally, I see this as a feature instead of a bug. A long-timer has earned their reputation (assuming that&amp;rsquo;s how they are a long-timer and not just a bad engineer successfully avoiding accountability at every turn) and can be afforded to be treated with more leniency in their code. A &lt;em&gt;newbie&lt;/em&gt; however benefits from stricter code review feedback teaching them the style and tradeoffs the team values when it comes to coding patterns, thus learning good practices along the way.&lt;/p&gt;
&lt;p&gt;In an ideal world, teams would have detailed style guides on how everything is written and every new person will be given ample time to read and understand them as part of onboarding. But the reality is that this rarely happens between the outdated style guides and the lack of proper onboarding process, so code review frequently becomes the first time a new person has the opportunity to learn about a team&amp;rsquo;s style guide (aside from linters and formatters). Let&amp;rsquo;s make sure to get it right so they don&amp;rsquo;t develop the wrong habits.&lt;/p&gt;
&lt;p&gt;That said, it&amp;rsquo;s important to note that senior engineers should still hold themselves to a higher level in producing quality code as a way to lead by example. It will be rather unconvincing to a newbie if their reviewer writes &lt;em&gt;subpar code&lt;/em&gt; while holding others to a higher quality bar.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#ask-for-further-details&#34;&gt;
    &lt;h2 id=&#34;ask-for-further-details&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Ask for further details&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Sometimes the change request itself is lacking details about the project or the reason behind such changes. Always ask for further details. In many occasions, it&amp;rsquo;s just the author overlooking it or making an assumption that their reviewers share the same level of context the author has about the project. This is a bit of an extension from the previous section but if you are accepting without understanding &lt;em&gt;why&lt;/em&gt;, you are in a sense &amp;ldquo;blindly accepting&amp;rdquo; the change (even if in this case, it&amp;rsquo;s just &lt;em&gt;partially&lt;/em&gt; blind lol).&lt;/p&gt;
&lt;p&gt;Similarly, if the change is too complex (or if it&amp;rsquo;s bundling too many changes at once), ask for it to be broken down into atomic changes for better / easier review. Having smaller changes also allows for each change to be better reviewed by different domain experts instead of everyone crowding on the same change request. (I understand this is not always feasible so there will be exceptions but ideally, this is done whenever possible.)&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#never-blindly-accept--accept-to-unblock&#34;&gt;
    &lt;h2 id=&#34;never-blindly-accept--accept-to-unblock&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Never blindly accept / accept to unblock&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;These are generally two separate scenarios but their result is generally the same. A change that is otherwise not meeting the code review bar is getting accepted and pushed to production. Blind acceptance generally reflects more of a bad team culture where people either don&amp;rsquo;t take code review seriously or they don&amp;rsquo;t feel comfortable pushing back in code review, both of which are bad. &amp;ldquo;Accept to unblock&amp;rdquo; generally comes with good intention but doing so essentially voids the role of a reviewer. The most common reason for its use is due to reviewers not feeling comfortable about blocking a change but not wishing to take responsibility if that ends up being the bad commit causing an outage.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;only&lt;/strong&gt; exception here would be when it&amp;rsquo;s a &lt;em&gt;time sensitive&lt;/em&gt; change where &amp;ldquo;it can&amp;rsquo;t be worse than it is now&amp;rdquo; &lt;strong&gt;and&lt;/strong&gt; that the author is the &lt;em&gt;domain expert&lt;/em&gt; of the change. The most common example being a code change attempting to mitigate an ongoing production issue. (I&amp;rsquo;ll write up an incident runbook at some point which will most likely include this.)&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#fix-now-not-later&#34;&gt;
    &lt;h2 id=&#34;fix-now-not-later&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Fix &lt;strong&gt;now&lt;/strong&gt; not later&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;On some occasions, you might run into a response in your review that they will &amp;ldquo;fix it in a future change&amp;rdquo;. (Generally, it&amp;rsquo;s more common in scenarios where your work adopts the idea of &lt;a href=&#34;https://graphite.dev/guides/stacked-diffs&#34;&gt;&amp;ldquo;stacked diffs&amp;rdquo;&lt;/a&gt;.) This is a bad pattern that should be avoided at large (unless &lt;em&gt;absolutely necessary&lt;/em&gt;) mainly because this change now hinges on a separate future change to be &lt;strong&gt;correct&lt;/strong&gt;, and that the other change could be independently reverted - for any external reasons - without this change, thus causing this bad pattern to now persist in your codebase.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#involve-the-subject-matter-expert&#34;&gt;
    &lt;h2 id=&#34;involve-the-subject-matter-expert&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Involve the Subject Matter Expert&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;If you&amp;rsquo;re tagged / asked to review a change but you think someone else should take a look too, tag them. Most people are very open to reviewing changes when approached directly (especially if they are the SME, the change likely affects them / their team directly too). I&amp;rsquo;ve lost count of the number of times where I wrote some code, pinged the SME, then learned that whatever I&amp;rsquo;m doing is an anti-pattern which &amp;ldquo;works fine now&amp;rdquo; but has no guarantee that it will continue working that way. Funnily, I&amp;rsquo;ve even had an experience where the framework team pulled me in to review changes because I was considered the SME for the use case another person was attempting. In general, it&amp;rsquo;s better to have these discussions &lt;em&gt;before&lt;/em&gt; a change is committed instead of &lt;em&gt;after&lt;/em&gt; (especially if it ends up causing unexpected incidents later on).&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#prototyping&#34;&gt;
    &lt;h2 id=&#34;prototyping&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Prototyping&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Code changes that are meant to be prototypes (especially RFCs) should clearly indicate that in their title (or some obvious tags / flags). There are 2 types of prototype code, one which is more of an exploration that is never meant to be shipped; while the other is a recommendation for adoption. In both scenarios, there should be clear indication of which type of prototype code this is and what the intention behind the prototype is (what are you trying to achieve?). For the former - exploration that is never meant to be shipped, the change should also clearly mark that it should never be shipped (hidden / unpublished etc.) with little comments all over explaining each of the &amp;ldquo;mocks&amp;rdquo; or &amp;ldquo;overrides&amp;rdquo; done to achieve the exploration. For the latter - recommendation for adoption, the change should be reviewed as strictly as any regular changes (potentially stricter since it&amp;rsquo;s likely coming from a &lt;em&gt;newbie&lt;/em&gt; as defined above).&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#long-review-time&#34;&gt;
    &lt;h2 id=&#34;long-review-time&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Long review time&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;This is more of a polarizing problem where your team either never heard of this problem, or it&amp;rsquo;s something your team is struggling immensely with. In some teams, the lack of proper &amp;ldquo;incentive&amp;rdquo; for doing code review has greatly reduced team velocity in shipping due to time taken for a code change sitting around waiting for it to be reviewed. While I&amp;rsquo;d usually chalk this up as an &amp;ldquo;incentive&amp;rdquo; problem, I personally believe this as a &amp;ldquo;lead by example&amp;rdquo; cultural shift. Usually teams / orgs better at this have more &lt;em&gt;technically driven&lt;/em&gt; high level ICs who do a lot of (strict?) code reviews themselves, thus fostering a good code review culture throughout their team / org. So in that sense, &amp;ldquo;be the change you want to see&amp;rdquo;.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#wrap-up&#34;&gt;
    &lt;h2 id=&#34;wrap-up&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Wrap up&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;The culture of code review is something very intentional and will take effort to both cultivate and maintain. The value of good culture however, is usually not something directly measurable (will write a separate piece about intangibles in the future). Having bad culture generally isn&amp;rsquo;t something immediately obvious nor is it something that can be turned around overnight, but rather something that&amp;rsquo;s simmered over a long period of time until it&amp;rsquo;s boiling, by then it&amp;rsquo;s &lt;em&gt;too late&lt;/em&gt; and people are leaving in droves.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Building a Simple Newsletter Service</title>
      <link>https://binhong.me/blog/2025-05-21-building-a-simple-newsletter-service/</link>
      <pubDate>Wed, 21 May 2025 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2025-05-21-building-a-simple-newsletter-service/</guid>
      <description>&lt;p&gt;When I had the idea on the series about &lt;a href=&#34;https://binhong.me/blog/2025-05-04-the-opinionated-engineer/&#34;&gt;The Opinionated Engineer&lt;/a&gt;, I figured maybe that&amp;rsquo;s something worth setting up a newsletter service over. That said, after some consideration, I just thought that I didn&amp;rsquo;t need anything too complex and decided to roll my own because &amp;ldquo;how hard could it be?&amp;rdquo;. As far as I can tell, I think it&amp;rsquo;s working fine so far(?) and took me around one whole day to put the whole thing together from my couch. You can find all of it&amp;rsquo;s source code &lt;a href=&#34;https://github.com/binhonglee/subscription&#34;&gt;here on GitHub&lt;/a&gt;.&lt;/p&gt;
        
&lt;a class=&#34;anchor&#34; href=&#34;#existing-newsletter-services&#34;&gt;
    &lt;h2 id=&#34;existing-newsletter-services&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Existing newsletter services&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Aside from Substack, pretty much all other services provide it with a &amp;ldquo;freemium&amp;rdquo; model where you are charged based on the number of subscribers (regardless if they are paid). Admittedly, I doubt my newsletter would get to that point (I try to not take myself too seriously lol) but I also didn&amp;rsquo;t need most of the things those services are providing. Not to take anything away from them, they all seem really useful if someone is doing that for a living, but I mostly write for fun and as a way to do some knowledge preservation (future self-reference) with the sharing part mostly being a bonus.&lt;/p&gt;
&lt;p&gt;As for Substack, I didn&amp;rsquo;t look too deep into things and while they don&amp;rsquo;t have the best of reputation, their product seems to fit my use case best where I won&amp;rsquo;t have to pay anything ever unless I started having paid subscribers. That said, I thought I didn&amp;rsquo;t need something as fancy but just something simple that goes with my personal blog.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#design&#34;&gt;
    &lt;h2 id=&#34;design&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Design&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;It&amp;rsquo;s a pretty straightforward design with a DB storing the email, a key (used in place of the email as an identifier), and it&amp;rsquo;s confirmation status. When someone subscribes, they get sent an email to confirm their subscription, which they can pick to either &amp;ldquo;Confirm&amp;rdquo; (where we update the confirmation status) or &amp;ldquo;Unsubscribe&amp;rdquo; (where the entry is removed from the DB). First reason to use a key is so that we know the confirmation or unsubscribe click is coming from the users&amp;rsquo; email instead of just people manually typing the link with someone else&amp;rsquo;s email as the param. Secondly, it prevents unintentional leak of the user&amp;rsquo;s email in address bar / browser history. Not necessarily a big deal but just not the ideal privacy design. (You could argue that &lt;em&gt;exposing&lt;/em&gt; the key that way isn&amp;rsquo;t great either but I&amp;rsquo;d imagine being unintentionally unsubscribed from &lt;del&gt;a&lt;/del&gt; my newsletter is likely a non-issue for most people lol.)&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#python-core-library-only&#34;&gt;
    &lt;h2 id=&#34;python-core-library-only&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Python core library only&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;The whole thing is written in Python (with some HTML templates) without using any third party library. I can&amp;rsquo;t say this was fully intentional but I generally like to avoid external libraries unless absolutely necessary which in this case, it&amp;rsquo;s not. As for &amp;ldquo;why Python&amp;rdquo;, it was an arbitrary decision as I&amp;rsquo;ve been doing interviews with Python and sometimes feel like I probably should brush up on it&amp;rsquo;s syntaxes lol. (Kept having to Google random things like &amp;ldquo;split string in Python&amp;rdquo;, &amp;ldquo;define object in Python&amp;rdquo; is uhh, not exactly ideal while in the middle of a coding interview session lol.) I did regret on this slightly because this can&amp;rsquo;t be &amp;ldquo;compiled into a binary&amp;rdquo; where it can just be dumped somewhere to run by itself forever with some config files and HTML files. Instead, it needs the server to have Python installed and run the actual Python code. I&amp;rsquo;d prefer keeping and running only the binary on server but I digress.&lt;/p&gt;
&lt;p&gt;As for it being HTML / CSS only, it&amp;rsquo;s mostly an extension from my personal site and blog being that way &lt;a href=&#34;https://binhong.me/blog/2022-01-31-rebuild-personal-site/&#34;&gt;which I&amp;rsquo;ve previously discussed about here&lt;/a&gt;. My personal opinion is that our growing reliance on JavaScript to do things had made us forget how powerful raw HTML and CSS could be in general. But also, I&amp;rsquo;ve been pleasantly surprised at how maintainable this has made my site as a whole. No worries about vulnerabilities or chasing and rebuilding it on the next / new shiny framework.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#email-html-quirks&#34;&gt;
    &lt;h2 id=&#34;email-html-quirks&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Email HTML quirks&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;For the most part, email HTML behaves mostly the same with regular page HTML but there&amp;rsquo;s a few things that didn&amp;rsquo;t work which I&amp;rsquo;d like to point out:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;button type=&amp;quot;submit&amp;quot; /&amp;gt;&lt;/code&gt; within a &lt;code&gt;&amp;lt;form method=&amp;quot;post&amp;quot; /&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;var()&lt;/code&gt; CSS params&lt;/li&gt;
&lt;li&gt;&lt;code&gt;html {}&lt;/code&gt;, &lt;code&gt;:root {}&lt;/code&gt;, &lt;code&gt;body {}&lt;/code&gt; selector in CSS&lt;/li&gt;
&lt;li&gt;&lt;code&gt;:hover&lt;/code&gt; - I read that some email provider supports them but most don&amp;rsquo;t&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;style /&amp;gt;&lt;/code&gt; block (Gmail client specific) - Despite &lt;a href=&#34;https://developers.google.com/workspace/gmail/design/css&#34;&gt;what Google says&lt;/a&gt;, only inline CSS is rendered on Gmail&amp;rsquo;s own clients*&lt;/li&gt;
&lt;li&gt;JavaScript - expected to prevent XSS lol&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I also tried to include everything in the email itself and didn&amp;rsquo;t make any references to external files for CSS which I assume would likely be stripped off as well.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;*The content of the email do remain so if you use a 3rd party client (like Mail on iOS) it&amp;rsquo;d render just fine but Gmail apps or web client won&amp;rsquo;t render anything in &lt;code&gt;&amp;lt;style /&amp;gt;&lt;/code&gt; tag.&lt;/em&gt;&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#credential-stuffing&#34;&gt;
    &lt;h2 id=&#34;credential-stuffing&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Credential stuffing&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Shortly after I deployed it to the open internet (like within 48 hours), I started seeing weird traffic with &lt;a href=&#34;https://www.threads.com/@binhonglee/post/DJ4rP3FyZsn&#34;&gt;random emails signing up but never confirming their sign-ups&lt;/a&gt;. &lt;a href=&#34;http://shub.club/&#34;&gt;Shubham&lt;/a&gt; mentioned that this is a &amp;ldquo;stuffing attack&amp;rdquo;. I did some more digging and find that this is a common thing where bad actors would just randomly try (usually old hacked) credentials on your site to see if they work. In my case, my param is set as &lt;code&gt;email&lt;/code&gt; so it&amp;rsquo;s likely super obvious that it takes an email. To be quite honest, I haven&amp;rsquo;t figure this all out but I added some secret key and some extra logging to track bad ips. It&amp;rsquo;s not perfect but it seems to be doing it&amp;rsquo;s job so far (along the confirmation check) in blocking majority of the spam traffic from actually signing up. As to why this is bad, I woke up to multiple emails mentioning that my send attempt to certain emails were bounced 🙃, probably a bad reputation thing if my email continues to be bounced.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#wrap-up&#34;&gt;
    &lt;h2 id=&#34;wrap-up&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Wrap up&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;This is a fun little experience and mostly a good opportunity to brush up my Python skill better. (Ironically, I ended up doing my next interview in TypeScript lol.) I&amp;rsquo;d imagine if I started having more, different, thematic series in the future, it might make sense to allow for segmented subscription / un-subscription but that&amp;rsquo;s work for another day. For now, I hope you enjoy reading this as much as I had fun building and writing this. Please subscribe below (or don&amp;rsquo;t lol).&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>The Art of Posting</title>
      <link>https://binhong.me/blog/2025-05-16-art-of-posting/</link>
      <pubDate>Fri, 16 May 2025 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2025-05-16-art-of-posting/</guid>
      <description>&lt;p&gt;This piece is specifically more about Meta&amp;rsquo;s &lt;em&gt;unique&lt;/em&gt; culture of &amp;ldquo;posting&amp;rdquo; about things but I imagine it can be a useful reference on communication in general. Many people loathe this process and complain about how it&amp;rsquo;s just a lot of &amp;ldquo;self-promotion&amp;rdquo; and while there&amp;rsquo;s definitely some truth to it, it&amp;rsquo;s also a valuable communication avenue that you can leverage to your advantage. As we will explore further below, &lt;em&gt;posting&lt;/em&gt; by itself is more of a bonus as the post will still require actual substance (which is &lt;em&gt;your work&lt;/em&gt; lol).&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is part of a series &lt;a href=&#34;https://binhong.me/blog/2025-05-04-the-opinionated-engineer/&#34;&gt;(The Opinionated Engineer)&lt;/a&gt; where I share my strong opinions on engineering practices.&lt;/em&gt;&lt;/p&gt;
        
&lt;a class=&#34;anchor&#34; href=&#34;#proposing-an-idea&#34;&gt;
    &lt;h2 id=&#34;proposing-an-idea&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Proposing an idea&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;A common theme of complaints you might see in a work environment is how &lt;em&gt;someone else&lt;/em&gt; steals your idea. Making a post serves as a paper-trail on this being &lt;em&gt;your idea&lt;/em&gt; as the original author. It&amp;rsquo;s also a useful cross-referencing tool when you run into discussions where your (previously suggested) idea could be a solution, you can share a link instead of going through your idea all over again. By itself, it also serves as a valuable RFC where others are free to comment with their concerns and / or how they feel about things in general. From there, you could gather feedback or even retool your idea a little to eventually become a valuable proposal and contribution towards your team goal.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#tracking-progress&#34;&gt;
    &lt;h2 id=&#34;tracking-progress&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Tracking progress&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;When working on a long running project (especially one involving many different people in different stages), posts serve as a good way to track and announce progress, signaling to others if / when they need to start getting involved or otherwise. It&amp;rsquo;s also a valuable communication tool where others can keep track of your project if it ended up affecting their own project (intentionally or otherwise). One of the more common &amp;ldquo;behavioral&amp;rdquo; gap issue for an E4 -&amp;gt; E5 promo is where an E4 can quietly work through the project and launch everything themselves while an E5 would communicate this clearly (outwardly) before even starting to ensure that their project does not end up conflicting with the timeline of other partner teams unintentionally.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#wrapping-up-a-project&#34;&gt;
    &lt;h2 id=&#34;wrapping-up-a-project&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Wrapping up a project&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Often times (especially on a long running complex project) by the time it comes to &lt;em&gt;finally&lt;/em&gt; closing out the project, everyone is exhausted and you&amp;rsquo;re just glad that &amp;ldquo;it&amp;rsquo;s done&amp;rdquo; and you can finally move on to the next thing. This is frequently a missed opportunity to nicely wrap up the project and show your appreciation towards everyone who helped make it happen. It&amp;rsquo;s like you went through all the trouble picking the perfect gift, understanding which variant fits them best, finding where to get one, but didn&amp;rsquo;t bother to wrap the gift in a wrapper or a nice little gift bag. Nothing wrong with it, and the value of the gift is still good but having a nice wrapper or even just a little ribbon would&amp;rsquo;ve been a cherry on top of the perfect gift. Similarly, making a post wrapping up the launch and sending thanks to everyone who supported the launch goes a long way in terms of both visibility and relationship building.&lt;/p&gt;
&lt;p&gt;Not all projects are straight up successful launches and / or without leftover cleanup work to do. In fact most projects have some level of cleanup work remaining after it&amp;rsquo;s launched. The wrap up post is the best time to clearly lay them out and at least ensure that they are tracked. Similarly, you might have learnings from the project &lt;em&gt;(especially for failed launches)&lt;/em&gt; which can be useful to share for future references if someone were to run into similar issues or to re-attempt this project again in the future.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#calling-out-potential-issues&#34;&gt;
    &lt;h2 id=&#34;calling-out-potential-issues&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Calling out potential issues&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Honestly this section kinda feels like just spelling out &amp;ldquo;regular social interactions&amp;rdquo; but sometimes it just needs to be said. Both as an author or as a reader of a post, you should take the opportunity to raise any potential issue you might be concern about in the comment of the post. As an author, this allows you to essentially crowdsource potential solution to your concern (maybe someone else has a &amp;ldquo;solution looking for a problem&amp;rdquo; lol). As a reader, you might have different perspective that sees potential risks that the author might have missed, or they figured it out but made an assumption that others would know, your question allows them to clarify their thought process or solution to your question.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;There are some exception where if you think something is not suitable to be discussed in such an &lt;strong&gt;open&lt;/strong&gt; setting but that&amp;rsquo;s largely up to your own discretion.&lt;/em&gt;&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#pre-read-from-subject-matter-experts&#34;&gt;
    &lt;h2 id=&#34;pre-read-from-subject-matter-experts&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Pre-read from Subject Matter Experts&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Sometimes you have an idea on stuff you might not be super familiar with (or at least not something you&amp;rsquo;re seen as an expert) and that&amp;rsquo;s fine. But it can be valuable to leverage people around you for their expert opinion. It&amp;rsquo;s also a great way to build work relationship as people like to be seen as an expert. I&amp;rsquo;ve largely found most people to be helpful and open to help look at whatever you&amp;rsquo;ve written as long as you have some level of relationship with them (be it working on a previous project together or trauma bonding through SEVs). Having a SME review your post provides credibility to the things you&amp;rsquo;re writing / proposing and helps get your stuff attention from the correct stakeholders.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#wrap-up&#34;&gt;
    &lt;h2 id=&#34;wrap-up&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Wrap up&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Admittedly, this is less of an opinion piece and more of a &amp;ldquo;how to&amp;rdquo; (or &amp;ldquo;how I do x&amp;rdquo;) piece. I don&amp;rsquo;t expect this to change minds entirely on your take about how this is just a lot of self-promotion theatrics but I hope to at least provide a framework on how to use this to your own advantage.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Push Fearlessly with Automated Testing</title>
      <link>https://binhong.me/blog/2025-05-04-push-fearlessly-with-automated-testing/</link>
      <pubDate>Sun, 04 May 2025 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2025-05-04-push-fearlessly-with-automated-testing/</guid>
      <description>&lt;p&gt;Conventional wisdom is that writing tests slows you down, because the alternative is that you don&amp;rsquo;t need to write tests. In fact, Meta (then Facebook) was famously lacking of automated test inline with it&amp;rsquo;s famous &amp;ldquo;move fast and break things&amp;rdquo; mantra. However, from what I&amp;rsquo;ve seen, I beg to differ.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is part of a series &lt;a href=&#34;https://binhong.me/blog/2025-05-04-the-opinionated-engineer/&#34;&gt;(The Opinionated Engineer)&lt;/a&gt; where I share my strong opinions on engineering practices.&lt;/em&gt;&lt;/p&gt;
        
&lt;a class=&#34;anchor&#34; href=&#34;#when-to-write-tests&#34;&gt;
    &lt;h2 id=&#34;when-to-write-tests&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;When to write tests?&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;blockquote&gt;
&lt;p&gt;If you love something, put a &lt;del&gt;ring&lt;/del&gt; test on it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I find this quote as a good general guiding principle. You should definitely have tests for all the &amp;ldquo;core&amp;rdquo; features. For all the additional features (or edge case handling), you&amp;rsquo;d need to make a decision / tradeoff based on how critical it is for something to work, and how frequent you might be changing it.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#fearlessly&#34;&gt;
    &lt;h2 id=&#34;fearlessly&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Fearlessly&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;The main goal of writing tests is to prevent regressions. Without automated tests, you&amp;rsquo;d have to rely on other (less reliable) ways to avoid regression. Having (non-flaky) tests makes it easy to consistently verify when / how a regression is introduced. With the right CI system setup, you can make sure that you don&amp;rsquo;t release new versions / feature that would accidentally break previously released features in an unexpected way.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#assumptions--prerequisite&#34;&gt;
    &lt;h2 id=&#34;assumptions--prerequisite&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Assumptions / Prerequisite&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;The expectation here is that you already have a working CI/CD pipeline setup which comprehensively runs all your tests for each commit merges and deployments. Alternatively, if you have too big of a test suite to run (or too costly), you could instead only run relevant tests on each individual commits while running all tests for each deployment.  If there’s a test that failed before deployment, you could still bisect to find the blame commit that broke the test.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#what-about-manual-testing&#34;&gt;
    &lt;h2 id=&#34;what-about-manual-testing&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;What about manual testing?&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;The main issue with manual testing is that you don&amp;rsquo;t know what you don&amp;rsquo;t know. You know your code change affects x and y so you test them both and they work as intended but what you didn&amp;rsquo;t realize was that it also breaks z entirely which you did not test because that was unexpected. Having an automated test means you&amp;rsquo;d just always run the test suite (within your CI/CD process) which would be able to catch the issue.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#what-about-qa-testing&#34;&gt;
    &lt;h2 id=&#34;what-about-qa-testing&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;What about QA testing?&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;QA testing generally happens on a weekly or bi-weekly (either definition) cadence. At best, you can do them on a daily basis but depending on the scale or speed at which your team is executing, it might still be hard to narrow down the specific commit that is causing the regression. With automated tests, you can bisect through your commit history without having to worry about setup and cleanup steps (assuming your tests are properly written).&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#why-not-just-write-test-for-everything&#34;&gt;
    &lt;h2 id=&#34;why-not-just-write-test-for-everything&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Why not just write test for everything?&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;In an ideal world where the cost to write test is 0, this would be a prescribed solution and everyone will be adopting TDD (Test Driven Development) but instead, we live in a real world where writing tests cost time and energy so it&amp;rsquo;s important to pick and choose things worth investing on vs things that aren&amp;rsquo;t. Such tradeoff is subjective to the criticality of said system and how often the system might be changed / updated.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#broken-tests&#34;&gt;
    &lt;h2 id=&#34;broken-tests&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Broken Tests&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Broken tests are &lt;strong&gt;tech debts&lt;/strong&gt;. Either prioritize fixing them or delete them if they are no longer relevant. Keeping them around gives false impression on the state of the test coverage while potentially adds unnecessary operational cost in running these tests. Same applies to flaky tests. In fact, I&amp;rsquo;d go a step further by saying that flaky tests are worse than broken tests because at least broken tests are deterministic (which is one important characteristic of a quality test).&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#wrap-up&#34;&gt;
    &lt;h2 id=&#34;wrap-up&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Wrap up&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;When you have working quality automated tests, you can push new changes fearlessly knowing that it won&amp;rsquo;t cause regressions on features protected by these tests. This allows you to move faster both in terms of thinking about and testing all the possible regressions your change might cause, while also saving time on root causing a regression (if / when it does happen).&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Self-Hosted GitHub Actions Runners</title>
      <link>https://binhong.me/blog/2025-04-25-self-hosted-github-runners/</link>
      <pubDate>Fri, 25 Apr 2025 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2025-04-25-self-hosted-github-runners/</guid>
      <description>&lt;p&gt;This post will focus more on the &amp;ldquo;why&amp;rdquo; and some of the quirks around doing so instead of the &amp;ldquo;how&amp;rdquo; since GitHub already have &lt;a href=&#34;https://docs.github.com/en/actions/hosting-your-own-runners&#34;&gt;a very comprehensive guide&lt;/a&gt; around it.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Just wanted to take a bit of time to acknowledge that it&amp;rsquo;s been almost 3 years since I last wrote a blog post. It&amp;rsquo;s been a busy 3 years for sure but hopefully I&amp;rsquo;ll be writing more consistently moving forward. (I do have quite a few half-written drafts that I&amp;rsquo;ve never published which I can&amp;rsquo;t tell if it&amp;rsquo;s still relavant at this point.)&lt;/em&gt;&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#whats-a-github-actions-runner&#34;&gt;
    &lt;h2 id=&#34;whats-a-github-actions-runner&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;What&amp;rsquo;s a GitHub Actions Runner?&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Basically instead of running GitHub Actions (GitHub&amp;rsquo;s own CI/CD) on GitHub&amp;rsquo;s machines, you can run them on your own devices. Running GitHub Actions on your self-hosted GARs would not count against your monthly Action minutes (since you are not using their machines) and can be an effective cost saving alternative.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#why-do-i-even-need-to-self-host-it&#34;&gt;
    &lt;h2 id=&#34;why-do-i-even-need-to-self-host-it&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Why do I even need to self-host it?&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;The main use-cases is when you have private GitHub repositories so you only have 2000 included Actions minutes. While it might make sense for you to pay for GitHub Pro (which comes with more GitHub minutes), I didn&amp;rsquo;t find myself needing all other features that comes with it. On top of that, I happen to have a few old computers lying around so I figured I&amp;rsquo;d use them as ever-on GitHub runners. (So far, the electricity bill seems to be cheaper than paying for extra minutes or paying for any external equivalent CI service.)&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#why-not-gitea-runners-or-other-self-hosted-ci-services&#34;&gt;
    &lt;h2 id=&#34;why-not-gitea-runners-or-other-self-hosted-ci-services&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Why not Gitea Runners (or other self-hosted CI services)?&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Personally, I already have GitHub Actions setup so migrating to a different CI service will take additional effort while still having similar limitations (for free plans). That said, Gitea actions mostly allows you to reuse GitHub action yamls (with a few minor exceptions like artifact upload / downloads). I stuck with using GitHub instead of fully migrating to self-hosted Gitea mainly due to the way dependabot and / or renovate-bot is already easily integrated without needing yet another action to trigger.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#linux-with-wsl&#34;&gt;
    &lt;h2 id=&#34;linux-with-wsl&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Linux (with WSL)&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;I have a &lt;em&gt;somewhat&lt;/em&gt; old Windows laptop (almost 6 years old, but broken battery and built-in keyboard) which is still pretty powerful aside from the fact that it can no longer be used as a laptop. It really hasn&amp;rsquo;t been used in any meaningful way for a while now so I decided to do some cleanup then setup multiple instances of WSL (with the same distro for convenience sake). The one issue I did ran into was when we needed postgres running in the background. Generally, you&amp;rsquo;d use a docker image of it on GH action but you actually can&amp;rsquo;t have multiple copies of postgres running on the same device because it&amp;rsquo;d complain about port 5432 being in use. (I tried blocking port sharing between instances but it didn&amp;rsquo;t seem to resolve the issue.) Eventually, I just install postgres on every instance but they all just uses the service from the first instance (which occupies the 5432 port). So far, it has mostly been working as expected.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#macos-with-utm&#34;&gt;
    &lt;h2 id=&#34;macos-with-utm&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;macOS (with UTM)&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;For the past few years, the only macOS device I own was a 2014 MacBook Air (which surprisingly still holds up&amp;hellip; to some extend). So when I decided to start building an iOS app for &lt;a href=&#34;https://globetrotte.com&#34;&gt;GlobeTrotte&lt;/a&gt;, I knew I needed to buy a new Mac device. I ended up getting a BNIB M4 Mac Mini from some dude on FB Marketplace (for like $500). That said, I slowly learnt the pain of an immobile dev device so I copped for a used M2 MacBook Air (also for around $500). With that, my Mac Mini is officially freed up to do whatever so I install UTM on it to replicate the WSL setup of having multiple VM running on a device. However, I quickly ran into storage limitation issue since I need at least ~50GB storage per instance in order to run iOS simulators with XCode on it. The main instance only have 256GB storage where more than half is already occupied with all sorts of things other stuff. So instead, I setup a HDD dock and tried running UTM images on them. For what it&amp;rsquo;s worth, it works but the experience wasn&amp;rsquo;t exactly great. The whole OS moves really slowly / laggy but I guess if it works, it works(?). I am contemplating on getting an external m.2 SSD to see if it&amp;rsquo;d do better. Will update this section of the post if I ended up doing so.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#pros&#34;&gt;
    &lt;h2 id=&#34;pros&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Pros&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Aside from the obvious advantages of not having to pay for CI fees and repurposing your old tech devices, it&amp;rsquo;s also valuable to have a consistent device setup especially across dev and CI environment (so you know test failures aren&amp;rsquo;t due to some missing setups). That said, I don&amp;rsquo;t think anything else particularly stood out aside from the cost factor and the ease of use (and it being &lt;em&gt;quasi-industry-standard&lt;/em&gt;).&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#cons&#34;&gt;
    &lt;h2 id=&#34;cons&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Cons&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;In my case, these are being ran on my personal devices at home so if my internet goes down, essentially my entire CI service goes down with it (which isn&amp;rsquo;t ideal). As mentioned above, I&amp;rsquo;ve also explored the option to host them on VPS but the cost (for a machine that can consistently run it) is really high. Lower tier VPS (like those available for $5/mo) generally have too little RAM causing it to freeze up a lot when running heavier operations (like e2e tests). Maybe I can bring an old computer to a friend&amp;rsquo;s / family&amp;rsquo;s house and run it on there but then you&amp;rsquo;d incur electricity bills at their place. (As far as I can tell, it&amp;rsquo;s not much but that&amp;rsquo;s generally not the way to treat people who trust you.)&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#wrap-up&#34;&gt;
    &lt;h2 id=&#34;wrap-up&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Wrap up&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;I did this mainly to save money and hassle to figure out which plan I need or what CI service fits me best. I used to run my own CI service with &lt;a href=&#34;https://drone.io&#34;&gt;drone.io&lt;/a&gt; many years ago &lt;a href=&#34;https://binhong.me/blog/2018-08-20-hosting-your-own-git-server-with-gitea/#droneio&#34;&gt;(which you can read more about here)&lt;/a&gt; but ran into the exact issue on VPS having rather limited RAM. Hopefully this setup can stay around for a bit longer and facilitate GlobeTrotte on &lt;em&gt;moving fast&lt;/em&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Automated Link Preview Image Card</title>
      <link>https://binhong.me/blog/2022-05-31-automated-link-preview-image/</link>
      <pubDate>Tue, 31 May 2022 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2022-05-31-automated-link-preview-image/</guid>
      <description>&lt;p&gt;A while ago, I saw &lt;a href=&#34;https://twitter.com/simonw/status/1300868423774212096&#34;&gt;this tweet thread from Simon Willison&lt;/a&gt; about how he added social media preview cards to his TILs. He detailed how he did this through a combination of Puppeteer, Vercel, SQLite, and some other stuff I didn&amp;rsquo;t understand 😅.&lt;/p&gt;
&lt;p&gt;At that time, I was manually taking these screenshots by hand to be included as part of the commit, which is to say it&amp;rsquo;s not very efficient. Anyway, this stayed on my backlog of &amp;ldquo;things to explore&amp;rdquo; for a really long time until recently during this long weekend (where I also took additional PTOs), I had some free time and decided to look into this. The goal is simple, automate creating the preview images for a list of given / defined urls.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#playwright&#34;&gt;
    &lt;h2 id=&#34;playwright&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Playwright&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;I know I just mentioned above that Simon used Puppeteer for his implementation but I just recently picked up Playwright for another project (&lt;a href=&#34;https://globetrotte.com&#34;&gt;GlobeTrotte&lt;/a&gt;) and figured that it seems like the right tool for this. Also, Playwright has better TypeScript support in general so I decided to opt for Playwright instead. (If you didn&amp;rsquo;t know, Playwright is built by the same team of engineers who built Puppeteer but has somehow all moved over from Google to Microsoft to build Playwright. There were some uhh interesting discussions about this when Playwright was first announced publicly.)&lt;/p&gt;
&lt;p&gt;The minimum code needed for a working version is pretty short. It looks something like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;chromium&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;playwright-core&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;async&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;genRun&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;Promise&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;void&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;browser&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;chromium&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;launch&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;env&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;browser&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;newContext&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;baseURL&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;http://localhost:1313/blog&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;viewport&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;width&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;1200&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;height&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;600&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;page&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;env&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;newPage&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;page&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;kr&#34;&gt;goto&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;/2022-05-31-automated-link-preview-image&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;page&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;waitForLoadState&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;networkidle&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;page&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;screenshot&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;animations&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;disabled&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;fullPage&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;jpeg&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;path&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;preview/automated_link_preview.jpg&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;browser&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;close&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;a class=&#34;anchor&#34; href=&#34;#globetrottealtimeter&#34;&gt;
    &lt;h2 id=&#34;globetrottealtimeter&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;@globetrotte/altimeter&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Since I might want to use this in multiple places, I figured it&amp;rsquo;s probably a good idea to create &lt;a href=&#34;https://www.npmjs.com/package/@globetrotte/altimeter&#34;&gt;an npm package&lt;/a&gt; for this so I can reuse it everywhere (with just some slight config change).&lt;/p&gt;
&lt;p&gt;The package itself is really just a CLI tool for now. First, create a json config file based on &lt;a href=&#34;https://github.com/binhonglee/GlobeTrotte/tree/main/src/altimeter#config&#34;&gt;this doc&lt;/a&gt; (depending on what you want from these screenshots). Then, run &lt;code&gt;npm i -g @globetrotte/altimeter &amp;amp;&amp;amp; altimeter config.json&lt;/code&gt; and you should see the screenshots show up once the run is complete.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: Unfortunately straight up calling &lt;code&gt;npx @globetrotte/altimeter config.json&lt;/code&gt; doesn&amp;rsquo;t work as of now (unless you already have playwright installed before which can&amp;rsquo;t be guaranteed) since it won&amp;rsquo;t trigger the postinstall script to install playwright.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Alternatively, it can also be generated by including the package as a dependency, then import &lt;code&gt;AltimeterConfig&lt;/code&gt; from the package, set the variables, and write the output to a JSON file. Something like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;AltimeterConfig&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;AltimeterDestination&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;@globetrotte/altimeter&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;async&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;genConfig&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;urls&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;config&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;AltimeterConfig&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;config&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;baseURL&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;http://localhost:1313/blog&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;config&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;destURLs&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;urls&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;map&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;((&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;url&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// You can also do some modification here to set a better &amp;#34;name&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;AltimeterDestination&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;url&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;url&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;c1&#34;&gt;// This line is to add the index page itself (omit if not needed)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;config&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;destURLs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;push&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;AltimeterDestination&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;index&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;/&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;config&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;dir&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;dist/preview&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;writeFileSync&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;join&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;__dirname&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;config.json&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;JSON&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;stringify&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;config&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;null&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Then you can run it with &lt;code&gt;altimeter config.json&lt;/code&gt; and it will start taking the screenshots and saving them into the specified folder.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#meta--tags&#34;&gt;
    &lt;h2 id=&#34;meta--tags&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;&lt;code&gt;&amp;lt;meta /&amp;gt;&lt;/code&gt; tags&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;To actually make the images show up when your web page is linked, you need to add the appropriate meta tags depending on which social media you want the image to be fetched and displayed.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#open-graph-protocol&#34;&gt;
    &lt;h3 id=&#34;open-graph-protocol&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Open Graph Protocol&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h3&gt;
&lt;/a&gt;
&lt;p&gt;&lt;a href=&#34;https://ogp.me/&#34;&gt;Open Graph Protocol&lt;/a&gt; is used on many web services (including Facebook, LinkedIn, Mastodon) to fetch the preview image so you need to add the followings onto your site within the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; element. You can also (optionally) specify the image width and height. From my personal experience, it doesn&amp;rsquo;t matter if they are absolute or relative path as they seem to work either way.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;head&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  ...
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;meta&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;property&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;og:image&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;content&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;preview/automated_link_preview.jpg&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;meta&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;property&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;og:image:height&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;content&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;600&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;meta&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;property&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;og:image:width&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;content&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;1200&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  ...
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;head&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;a class=&#34;anchor&#34; href=&#34;#twitter&#34;&gt;
    &lt;h3 id=&#34;twitter&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Twitter&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h3&gt;
&lt;/a&gt;
&lt;p&gt;Twitter has its &lt;a href=&#34;https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup&#34;&gt;own documentation&lt;/a&gt; for the display card. At the time of writing, it seems like they only take absolute path, relative paths don&amp;rsquo;t seem to work as intended.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;head&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  ...
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;meta&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;twitter:card&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;content&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;summary_large_image&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;meta&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;twitter:image&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;content&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;https://binhong.me/blog/preview/automated_link_preview.jpg&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  ...
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;head&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;You can use &lt;a href=&#34;https://cards-dev.twitter.com/validator&#34;&gt;this card validator&lt;/a&gt; built by Twitter to try if your newly set &lt;code&gt;&amp;lt;meta /&amp;gt;&lt;/code&gt; tags are getting picked up. Unlike just tweeting it out on your profile and see how it work, this would always force fetch from the given link and overwrite whatever cache they already stored of the link.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#google&#34;&gt;
    &lt;h3 id=&#34;google&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Google&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h3&gt;
&lt;/a&gt;
&lt;p&gt;Honestly, I couldn&amp;rsquo;t find a good documentation on how to go about setting the image properly. I only found &lt;a href=&#34;https://developers.google.com/search/docs/advanced/robots/robots_meta_tag#max-image-preview&#34;&gt;this&lt;/a&gt; about setting the &lt;code&gt;max-image-preview&lt;/code&gt; but nowhere does it say how to tell Google which image to use. I looked into how some of the news websites does it instead. NYTimes uses the &lt;code&gt;image&lt;/code&gt; tag as below. The Verge seems to use some sort of &lt;a href=&#34;https://schema.org/&#34;&gt;Schema.org&lt;/a&gt; setup but within a &lt;code&gt;&amp;lt;script /&amp;gt;&lt;/code&gt; tag (instead of &lt;code&gt;&amp;lt;meta /&amp;gt;&lt;/code&gt;) and label it as &lt;code&gt;application/ld+json&lt;/code&gt; type. I also checked GitHub (since they actually do one of these screenshot as preview image stuff really well) but I didn&amp;rsquo;t see any &lt;code&gt;&amp;lt;meta /&amp;gt;&lt;/code&gt; tag that stood out.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;head&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  ...
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;meta&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;robots&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;content&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;max-image-preview:large&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;meta&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;image&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;content&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;https://binhong.me/threads/preview/index.jpg&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  ...
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;head&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;a class=&#34;anchor&#34; href=&#34;#github-action&#34;&gt;
    &lt;h2 id=&#34;github-action&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;GitHub Action&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;I currently use the above package as a CLI on my own blog (specifically this one that you&amp;rsquo;re reading right now). I didn&amp;rsquo;t want to have to run a bunch of things manually everytime I write a new post before I deploy so instead, I &amp;ldquo;delegated&amp;rdquo; this part of the job to GitHub Action.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Add the generated directory to .gitignore&lt;/li&gt;
&lt;li&gt;Use Node.js in the GitHub Action (&lt;code&gt;- uses: actions/setup-node@v1&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Make sure the devserver site is up and running (in my case, &lt;code&gt;hugo serve -D&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;npm install&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;npx altimeter config.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Run build if needed (&lt;code&gt;hugo --buildFuture&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Deploy! 🎉&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;Note: If you are generating this for static content, you might need to setup a server to serve up those static content on a &lt;code&gt;localhost&lt;/code&gt; address. I recommend following &lt;a href=&#34;https://nodejs.org/en/knowledge/HTTP/servers/how-to-serve-static-files/&#34;&gt;this guide&lt;/a&gt; to set up a server (instead of using &lt;code&gt;expressjs&lt;/code&gt;).&lt;/em&gt;&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#wrap-up&#34;&gt;
    &lt;h2 id=&#34;wrap-up&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Wrap up&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;This was a fun ride. Honestly, I ran into a few problems here and there (mainly, setting up monorepo in GlobeTrotte and creating CLI with TypeScript) but it&amp;rsquo;s relatively straightforward otherwise. The actual coding part took less than a day while writing up the documentations (and this blog post) probably took close to double the time.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Rebuilding Personal Site from Scratch</title>
      <link>https://binhong.me/blog/2022-01-31-rebuild-personal-site/</link>
      <pubDate>Mon, 31 Jan 2022 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2022-01-31-rebuild-personal-site/</guid>
      <description>&lt;p&gt;As time goes on in my journey of experimenting with different technologies on building a web interface, I figured it&amp;rsquo;s time for my personal site to return to the fundamentals. So I decided to build it with only HTML + CSS with one of the goals being that it should work perfectly fine with JavaScript disabled.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Before this, it&amp;rsquo;s built with Vue. You can still find the source &lt;a href=&#34;https://github.com/binhonglee/binhonglee.github.io/tree/old_dev&#34;&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#design&#34;&gt;
    &lt;h1 id=&#34;design&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Design&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h1&gt;
&lt;/a&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#inspiration&#34;&gt;
    &lt;h2 id=&#34;inspiration&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Inspiration&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;&lt;em&gt;Skip this next paragraph if you&amp;rsquo;re not interested in my old site&amp;rsquo;s origin story.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;My old site is structured more like a resume than a personal self intro. It&amp;rsquo;s rather serious by nature since it was built at a time when I was looking my first job out of college. I stole the design from the personal site of a great friend of mine, Pat Pataranutaporn (just one out of the plethora of pages) and dulled it down a little. He rebuilt his entire personal site shortly after but I stick with it since I built it in a way that&amp;rsquo;s rather easy to maintain (as in adding and removing stuff overtime). It served me well during those times but at this point a redesign is rather long overdue.&lt;/p&gt;
&lt;p&gt;I can&amp;rsquo;t really do design (unless I&amp;rsquo;m given a rigid component library then maybe 😅) so I looked up how some of the seasoned software engineers design their site and pick out the parts I like about them. Here are some of the websites I took inspiration from:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://wongmjane.com/&#34;&gt;Jane Manchun Wong&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://cassidoo.co/&#34;&gt;Cassidy Williams&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://joeprevite.com/&#34;&gt;Joe Previte&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#ideology&#34;&gt;
    &lt;h2 id=&#34;ideology&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Ideology&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;One of the most obvious common traits among them is how minimal they are while still showing a lot of personality. The other being that 2 / 3 have a dark mode toggle while Jane&amp;rsquo;s site is dark mode only. I stick with these ideas and decided to be more playful with my text colors especially since I can&amp;rsquo;t draw.&lt;/p&gt;
&lt;p&gt;One other takeaway I had was that I really liked the monospaced font used on Cassidy&amp;rsquo;s site. I tried Victor Mono initially since I really like it (and use it day-to-day on all my text editors) but I find it&amp;rsquo;s readability isn&amp;rsquo;t great for longer paragraphs. I decided to settle for Fira Code instead (first font that introduced me to ligatures for programming).&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: Monospaced fonts in general aren&amp;rsquo;t meant for regular text reading anyway so this is something completely within expectation.&lt;/em&gt;&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#implementation&#34;&gt;
    &lt;h1 id=&#34;implementation&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Implementation&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h1&gt;
&lt;/a&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#dark-mode&#34;&gt;
    &lt;h2 id=&#34;dark-mode&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Dark mode&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Since I&amp;rsquo;m not using JavaScript, this is going to be tricky. That said, major credit to Shubham for pointing out the basic concept of its implementation in &lt;a href=&#34;https://twitter.com/4shub/status/1259298005368270849&#34;&gt;this tweet&lt;/a&gt;. On top of that, there&amp;rsquo;s just a lot of CSS gymnastics where I learn about the different selectors and what they each does in CSS (&lt;a href=&#34;https://www.w3schools.com/cssref/sel_element_pluss.asp&#34;&gt;+&lt;/a&gt;, &lt;a href=&#34;https://www.w3schools.com/cssref/sel_element_gt.asp&#34;&gt;&amp;gt;&lt;/a&gt;, &lt;a href=&#34;https://www.w3schools.com/cssref/sel_gen_sibling.asp&#34;&gt;~&lt;/a&gt;, &lt;a href=&#34;https://www.w3schools.com/cssref/sel_attribute.asp&#34;&gt;[]&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;One important thing of note is that, the toggle &amp;ldquo;button&amp;rdquo; (the &lt;code&gt;detail&lt;/code&gt; component) has to be at the same level as the top level of the rest of the components on the screen. I keep my toggle on the right which is sufficient to prevent colliding / blocked by other stuff on screen.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#smaller-screens&#34;&gt;
    &lt;h2 id=&#34;smaller-screens&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Smaller Screens&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;I tried my best to make everything as modular as possible so it should properly reorganize itself dynamically based on the given screen size. However, there are always exceptions and this is where &lt;code&gt;@media screen and (max-width: 1000px)&lt;/code&gt; comes in handy for addressing these edge cases.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#dropdown-menu&#34;&gt;
    &lt;h2 id=&#34;dropdown-menu&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Dropdown Menu&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;As I add a few more sections to my side, I started to realize that the link menu might not fit on certain display sizes. In general, it just follows &lt;a href=&#34;https://www.w3schools.com/Css/css_dropdowns.asp&#34;&gt;this w3c guide&lt;/a&gt;. I originally did one implementation that used the similar idea of the dark mode toggle above but I opted for &lt;code&gt;:hover&lt;/code&gt; instead of &lt;code&gt;[open]&lt;/code&gt; due to its interaction where I think it makes more sense to be dismissed when I tap on random places on the screen.&lt;/p&gt;
&lt;p&gt;In order to make it more accessible, it is also important to add the appropriate aria labels. &lt;a href=&#34;https://www.w3.org/TR/wai-aria-practices/examples/menu-button/menu-button-links.html&#34;&gt;This page&lt;/a&gt; has a rather comprehensive guide on the proper labels to add for each fields.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#truncation-somewhat-unrelated&#34;&gt;
    &lt;h2 id=&#34;truncation-somewhat-unrelated&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Truncation (somewhat unrelated)&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;This is more for the blog part of the site where I find the default truncation being too lenient thus generating some of the overly long (and not uniformed) summary text.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-css&#34; data-lang=&#34;css&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nc&#34;&gt;description&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;display&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kp&#34;&gt;-webkit-&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;box&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;overflow&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;hidden&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;word-break&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;break-word&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kp&#34;&gt;-webkit-&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;line-clamp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;3&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kp&#34;&gt;-webkit-&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;box-orient&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;vertical&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;a class=&#34;anchor&#34; href=&#34;#takeaway&#34;&gt;
    &lt;h1 id=&#34;takeaway&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Takeaway&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h1&gt;
&lt;/a&gt;
&lt;p&gt;Overall it&amp;rsquo;s been a great experience to relearn and understand what we can do even without any of the modern JavaScript frameworks. It also serves as a great reminder that web existed for quite a while before JavaScript came around. While they may not be as pretty and reactive as it is today, they still works and have a lot to offer (especially as HTML and CSS standards continues to evolve as well).&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Upgrading Vue TS Project (2.x -&gt; 3.x)</title>
      <link>https://binhong.me/blog/2021-12-12-vue-3-vite/</link>
      <pubDate>Sun, 12 Dec 2021 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2021-12-12-vue-3-vite/</guid>
      <description>&lt;p&gt;&lt;del&gt;While Vue 3 has been out of beta for quite a while, (&lt;a href=&#34;https://v3.vuejs.org/guide/migration/introduction.html#breaking-changes&#34;&gt;as noted on their website&lt;/a&gt;) a migration build is still a work in progress so the current advice is to stay with Vue 2 if it&amp;rsquo;s a non-trivial project.&lt;/del&gt; I procrastinated on this article for so long that a migration build has since been released. Feel free to check it out &lt;a href=&#34;https://v3.vuejs.org/guide/migration/migration-build.html&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;That said, as Parcel v1 is &lt;a href=&#34;https://www.npmjs.com/package/parcel-bundler/v/1.9.4&#34;&gt;no longer maintained&lt;/a&gt; (and with v2 supporting only Vue 3), I figured it&amp;rsquo;s time to upgrade.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#why-not-parcel-v2&#34;&gt;
    &lt;h2 id=&#34;why-not-parcel-v2&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Why not Parcel v2?&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;This is actually quite straightforward. I&amp;rsquo;ve been wanting to try out vite for a while since I first saw it mentioned on an esbuild issue (asking for vue SFC support). I did consider snowpack for a while but I still decided to stick with vite seeing how its also created by Evan You and it seems to have mildly better performance.&lt;/p&gt;
&lt;p&gt;I did initially try to upgrade the project to Parcel v2 seeing as &lt;a href=&#34;https://v2.parceljs.org/getting-started/migration&#34;&gt;how simple it was described as&lt;/a&gt;. However, &lt;a href=&#34;https://github.com/parcel-bundler/parcel/issues/4022&#34;&gt;I was wrong&lt;/a&gt; and instead of sinking anymore time into it, I decided to move onto something else.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#moving-onto-vite&#34;&gt;
    &lt;h2 id=&#34;moving-onto-vite&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Moving onto vite&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;The one thing that really sealed it for me (with vite) was how simple the move turned out to be. I created an empty project with their template, clone the relatively minimal default config over, and it&amp;rsquo;s &amp;ldquo;mostly&amp;rdquo; working. (Well, &amp;ldquo;mostly&amp;rdquo; because my project was still on Vue 2 instead of Vue 3.) Funnily enough, this was the same reason that led me to opting with Parcel initially since it needed very minimal configuration to get started.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#vue-router&#34;&gt;
    &lt;h2 id=&#34;vue-router&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;&lt;code&gt;vue-router&lt;/code&gt;&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;According to the official Vue 3 guide, the way to call the router is to import and call it the &lt;code&gt;VueRouter&lt;/code&gt; you defined. This is slightly different from the previous version where you call &lt;code&gt;this.router&lt;/code&gt; to use it. Well technically you can still do exactly that, as long as you define router in your global properties like below.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;createApp&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;vue&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;App&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;./App.vue&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;router&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;./router&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;app&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;createApp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;App&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;app&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;config&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;globalProperties&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;$router&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;router&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;app&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;use&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;router&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;At this point, router navigation should work just fine but you might see &lt;code&gt;tsc&lt;/code&gt; (or &lt;code&gt;vue-tsc&lt;/code&gt;) throwing type errors saying that &lt;code&gt;this.router&lt;/code&gt; is of type unknown. We&amp;rsquo;ll fix this later when discussing about &lt;code&gt;vue-tsc&lt;/code&gt;.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#vue-tsc&#34;&gt;
    &lt;h2 id=&#34;vue-tsc&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;&lt;code&gt;vue-tsc&lt;/code&gt;&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;One of the main upgrades that Vue 3 provides is &lt;code&gt;vue-tsc&lt;/code&gt;. On top of the regular type checking already available with &lt;code&gt;tsc&lt;/code&gt;, &lt;code&gt;vue-tsc&lt;/code&gt; also do type checking for inline TypeScript / Vue code in template (like &lt;code&gt;v-for&lt;/code&gt;, &lt;code&gt;v-if&lt;/code&gt;, &lt;code&gt;:props&lt;/code&gt;&amp;hellip;).&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#volar-vscode&#34;&gt;
    &lt;h3 id=&#34;volar-vscode&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Volar (vscode)&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h3&gt;
&lt;/a&gt;
&lt;p&gt;If you do your Vue project development on vscode, you probably have Vetur installed. While Vetur works with Vue 3 projects, Volar is an upgrade to that comparatively as it uses vue-tsc and displays those new type errors inline. They do not complement / work with each other though so make sure you remove one before you install the other.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#shims&#34;&gt;
    &lt;h2 id=&#34;shims&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Shims&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;So if you&amp;rsquo;re not already familiar with shims, it&amp;rsquo;s essentially a type declaration file to tell your IDE what are the types of the vue file exports (and tsx files). &lt;a href=&#34;https://stackoverflow.com/questions/54622621/&#34;&gt;Here&lt;/a&gt;&amp;rsquo;s a stackoverflow question on why it&amp;rsquo;s needed.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#shims-vuedts&#34;&gt;
    &lt;h3 id=&#34;shims-vuedts&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;&lt;code&gt;shims-vue.d.ts&lt;/code&gt;&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h3&gt;
&lt;/a&gt;
&lt;p&gt;This is the generic one that&amp;rsquo;d be generated when you create a new Vue project.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;declare&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;module&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;*.vue&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;DefineComponent&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;vue&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;component&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;DefineComponent&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;default&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;component&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;a class=&#34;anchor&#34; href=&#34;#shims-vue-run-timedts&#34;&gt;
    &lt;h3 id=&#34;shims-vue-run-timedts&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;&lt;code&gt;shims-vue-run-time.d.ts&lt;/code&gt;&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h3&gt;
&lt;/a&gt;
&lt;p&gt;If you make router calls in your code with &lt;code&gt;this.$router&lt;/code&gt; directly, you might find &lt;code&gt;vue-tsc&lt;/code&gt; complaining about missing type declaration for it even if you already declared &lt;code&gt;app.use(router);&lt;/code&gt; in your &lt;code&gt;main.ts&lt;/code&gt; file. It seems like that doesn&amp;rsquo;t inherit the types imported automatically so you need to declare it separately for &lt;code&gt;vue-tsc&lt;/code&gt; to know where to look.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;VueRouter&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;vue-router&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;declare&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;module&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;@vue/runtime-core&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;interface&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;ComponentCustomProperties&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;$router&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;VueRouter&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Of course, if you also call other stuffs similarly. As an example, let&amp;rsquo;s say you call &lt;code&gt;this.$notify&lt;/code&gt; with ElementPlus. You&amp;rsquo;d also include that in this type declaration file like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;ElNotification&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;element-plus&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;VueRouter&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;vue-router&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;declare&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;module&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;@vue/runtime-core&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;interface&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;ComponentCustomProperties&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;$notify&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;ElNotification&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;$router&lt;/span&gt;: &lt;span class=&#34;kt&#34;&gt;VueRouter&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;As for why I can&amp;rsquo;t just put these lines of codes in the same type declaration file as above, it threw me some error when I tried that. Separating them somehow solved the issue for me.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#indexhtml&#34;&gt;
    &lt;h2 id=&#34;indexhtml&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;index.html&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Usually this should just be in default state so the only change that needs to be made was to add this additional param of &lt;code&gt;type=&amp;quot;module&amp;quot;&lt;/code&gt; to the script tag calling the entrypoint.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;script&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;module&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;src&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;/src/main.ts&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;script&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;a class=&#34;anchor&#34; href=&#34;#pug&#34;&gt;
    &lt;h2 id=&#34;pug&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Pug&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;For pug in Vue 2, you can have your whole block indented (kinda like the regular html block). But in Vue 3, it&amp;rsquo;s no longer supported so your outer most &lt;code&gt;div&lt;/code&gt; can no longer be indented.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#wrap-up&#34;&gt;
    &lt;h2 id=&#34;wrap-up&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Wrap up&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Overall, there are some bumps here and there but the process is relatively easy to understand especially with the help of new tools like volar (and faster compilation of vite).&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>PostgreSQL on Azure Pipeline with OSX</title>
      <link>https://binhong.me/blog/2021-07-18-postgresql-on-azure/</link>
      <pubDate>Sun, 18 Jul 2021 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2021-07-18-postgresql-on-azure/</guid>
      <description>&lt;p&gt;While the defacto way of running postgres on Azure Pipeline according to &lt;a href=&#34;https://devblogs.microsoft.com/devops/using-containerized-services-in-your-pipeline/&#34;&gt;this guide&lt;/a&gt; seems to be using the docker image, the MacOS image on Azure Pipeline does not have docker support due to some &lt;a href=&#34;https://github.com/actions/virtual-environments/issues/2150&#34;&gt;licensing issue&lt;/a&gt;. So instead, this has to be setup and handled manually. We&amp;rsquo;d use brew here since it comes installed in the OS image and is the easiest way to deal with it as far as I&amp;rsquo;m aware.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#install-optional&#34;&gt;
    &lt;h2 id=&#34;install-optional&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Install (optional)&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;By default postgres should already be installed in the included MacOS image but if you prefer to run it on a specific version you&amp;rsquo;d have to force reinstall the version you want just to be sure.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;steps&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nn&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;- &lt;span class=&#34;nt&#34;&gt;script&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;brew install postgresql@13.1&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;displayName&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;Install PostgreSQL&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nn&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;a class=&#34;anchor&#34; href=&#34;#multiple-versions-optional&#34;&gt;
    &lt;h2 id=&#34;multiple-versions-optional&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Multiple Versions (optional)&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;If you&amp;rsquo;d like to test that it works with multiple versions of PostgreSQL, you can setup a version matrix for it.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;strategy&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;matrix&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;postgresql-11-10&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;psqlVersion&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;11.10&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;postgresql-13-1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;psqlVersion&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;13.1&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Then amend the installation part to make use of the value from the matrix.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;steps&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nn&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;- &lt;span class=&#34;nt&#34;&gt;script&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;brew install postgresql@$PSQL_VERSION&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;displayName&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;Install PostgreSQL&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;env&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;PSQL_VERSION&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;$(psqlVersion)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nn&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;a class=&#34;anchor&#34; href=&#34;#start-service&#34;&gt;
    &lt;h2 id=&#34;start-service&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Start Service&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Despite being installed by default, it doesn&amp;rsquo;t run by default so you&amp;rsquo;ll need to add something like this to get the service started.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;steps&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nn&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;- &lt;span class=&#34;nt&#34;&gt;script&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;brew services start postgresql&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;displayName&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;Start Postgres Service&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nn&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;a class=&#34;anchor&#34; href=&#34;#set-up-db&#34;&gt;
    &lt;h2 id=&#34;set-up-db&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Set up DB&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Now, let&amp;rsquo;s create a database called &lt;code&gt;db_name&lt;/code&gt; owned by the user called &lt;code&gt;username&lt;/code&gt; with &lt;code&gt;password&lt;/code&gt; as password. (You can replace each of these with whatever that works best for you / your process.)&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;steps&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nn&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;- &lt;span class=&#34;nt&#34;&gt;script&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;sd&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;    psql postgres -w -c &amp;#34;CREATE DATABASE db_name;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;    psql postgres -w -c &amp;#34;CREATE ROLE username WITH SUPERUSER CREATEDB LOGIN ENCRYPTED PASSWORD &amp;#39;password&amp;#39;;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;    psql postgres -w -c &amp;#34;GRANT ALL PRIVILEGES ON DATABASE db_name TO username;&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;displayName&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;Setup Database&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nn&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;a class=&#34;anchor&#34; href=&#34;#brew-optimization-optional&#34;&gt;
    &lt;h2 id=&#34;brew-optimization-optional&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Brew Optimization (optional)&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;At this point, postgres should already be working as intended but it&amp;rsquo;s not exactly ideal considering the first &lt;code&gt;brew&lt;/code&gt; call actually takes a long time since it&amp;rsquo;s also trying to run update and cleanup which can take a while. Considering that running brew update and brew cleanup probably has minimal benefit on a CI, we can add the following environment variables so brew wouldn&amp;rsquo;t do that.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;variables&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;HOMEBREW_NO_INSTALL_CLEANUP&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;HOMEBREW_NO_AUTO_UPDATE&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;a class=&#34;anchor&#34; href=&#34;#os-condition-optional&#34;&gt;
    &lt;h2 id=&#34;os-condition-optional&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;OS Condition (optional)&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;If you&amp;rsquo;re including this as part of a multi OS testing config, you can add an additional condition for each line of the commands here to make sure it doesn&amp;rsquo;t run on other OS-es causing unexpected failures. Similarly, you can add its equivalent for commands specific to other OS-es.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;steps&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nn&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;- &lt;span class=&#34;nt&#34;&gt;script&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;brew services start postgresql&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;displayName&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;Start Postgres Service&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;condition&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;eq(variables[&amp;#39;Agent.OS&amp;#39;], &amp;#39;Darwin&amp;#39;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nn&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;a class=&#34;anchor&#34; href=&#34;#full-config&#34;&gt;
    &lt;h2 id=&#34;full-config&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Full config&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Finally when put together, your config likely looks something like this.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;25
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;26
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;27
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;28
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;29
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;variables&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;HOMEBREW_NO_INSTALL_CLEANUP&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;HOMEBREW_NO_AUTO_UPDATE&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;pool&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;vmImage&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;macos-latest&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;strategy&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;matrix&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;postgresql-11-10&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;psqlVersion&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;11.10&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;postgresql-13-1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;psqlVersion&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;13.1&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;steps&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;- &lt;span class=&#34;nt&#34;&gt;script&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;brew install postgresql@$(psqlVersion)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;displayName&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;Install PostgreSQL&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;condition&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;eq(variables[&amp;#39;Agent.OS&amp;#39;], &amp;#39;Darwin&amp;#39;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;- &lt;span class=&#34;nt&#34;&gt;script&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;brew services start postgresql&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;displayName&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;Start Postgres Service&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;condition&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;eq(variables[&amp;#39;Agent.OS&amp;#39;], &amp;#39;Darwin&amp;#39;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;- &lt;span class=&#34;nt&#34;&gt;script&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;|&lt;/span&gt;&lt;span class=&#34;sd&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;    psql postgres -w -c &amp;#34;CREATE DATABASE db_name;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;    psql postgres -w -c &amp;#34;CREATE ROLE username WITH SUPERUSER CREATEDB LOGIN ENCRYPTED PASSWORD &amp;#39;password&amp;#39;;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sd&#34;&gt;    psql postgres -w -c &amp;#34;GRANT ALL PRIVILEGES ON DATABASE db_name TO username;&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;displayName&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;Setup Database&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;condition&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;eq(variables[&amp;#39;Agent.OS&amp;#39;], &amp;#39;Darwin&amp;#39;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Hopefully you find this article helpful!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Ava with Vue (2.x) &#43; TypeScript (and NYC)</title>
      <link>https://binhong.me/blog/2021-05-22-ava-with-vue-typescript-and-nyc/</link>
      <pubDate>Sat, 22 May 2021 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2021-05-22-ava-with-vue-typescript-and-nyc/</guid>
      <description>&lt;p&gt;When working on a project with Vue (TypeScript), I faced a lot of roadblocks trying to get code coverage to work. The issue is largely covered in &lt;a href=&#34;https://github.com/vuejs/vue-cli/issues/1363&#34;&gt;this issue thread&lt;/a&gt;. Most solutions / workarounds for that seems to revolve around changing the bundling flow with webpack. Considering my lack of familiarity with webpack (on top of my use of scss and pug in Vue files), putting together a webpack config that works for me turns out to be extremely challenging. That somewhat prompted me to move the build and test process away from &lt;code&gt;vue-cli&lt;/code&gt; while starting to experiment with different tools that could work. One thing to note is that, I&amp;rsquo;ll be using babel to handle TypeScript compiling / transpiling instead of &lt;code&gt;ts-node&lt;/code&gt; as recommended from Ava&amp;rsquo;s guide.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#babel&#34;&gt;
    &lt;h2 id=&#34;babel&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Babel&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;We use babel to help Ava properly transpile the Vue and TypeScript files. This is especially useful if you would like to get accurate code coverage for your tests.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s how the &lt;code&gt;.babelrc&lt;/code&gt; should look like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;presets&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;@babel/preset-env&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;@babel/preset-typescript&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;babel-preset-typescript-vue&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;plugins&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;@babel/plugin-transform-runtime&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;@babel/plugin-transform-typescript&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;s2&#34;&gt;&amp;#34;module-resolver&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;#34;alias&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;          &lt;span class=&#34;nt&#34;&gt;&amp;#34;@&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;./src&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;env&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;test&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nt&#34;&gt;&amp;#34;plugins&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;istanbul&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;A few things to note here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Alias set is optional. You only need it if you actually use the alias. You can also set multiple aliases or point it to a different directory depending on how your project is setup. Make sure you sync it with your &lt;code&gt;tsconfig.json&lt;/code&gt; to prevent confusion / unwanted errors&lt;/li&gt;
&lt;li&gt;&lt;code&gt;istanbul&lt;/code&gt; plugin is also optional. This is only needed if you want to also set up code coverage with &lt;code&gt;nyc&lt;/code&gt; (more on this later).&lt;/li&gt;
&lt;/ul&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#setup-file&#34;&gt;
    &lt;h2 id=&#34;setup-file&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Setup File&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Setup file is there to set the test environment considering that these Vue component files are supposed to be ran inside a Vue instance with some preset, this file serve as a similar purpose but limited only to testing use.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a sample &lt;code&gt;_setup.js&lt;/code&gt; where you can build on top of depending on what you need / use. See original reference &lt;a href=&#34;https://github.com/avajs/ava/blob/main/docs/recipes/vue.md&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;25
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;26
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;27
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;28
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;29
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;30
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;31
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;32
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;33
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;html&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;options&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;url&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;http://localhost/&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;require&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;jsdom-global&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;html&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;options&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Fix the Date object, see &amp;lt;https://github.com/vuejs/vue-test-utils/issues/936#issuecomment-415386167&amp;gt;.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;window&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;Date&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;Date&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Setup browser environment
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;hooks&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;require&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;require-extension-hooks&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;Vue&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;require&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;vue&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;Vue&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;config&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;productionTip&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;Vue&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;config&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;devtools&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;hooks&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;vue&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;).&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;plugin&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;vue&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;).&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;push&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;hooks&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;([&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;vue&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;ts&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;exclude&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(({&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;filename&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;})&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;filename&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;match&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sr&#34;&gt;/\/node_modules\//&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;plugin&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;babel&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;push&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;window&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;localStorage&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nb&#34;&gt;window&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;localStorage&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;getItem&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;{}&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;setItem&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;clear&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;localStorage&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;window&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;localStorage&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;One of the changes I made was to set &lt;code&gt;localStorage&lt;/code&gt; so you can also use it for the test.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ll also need to include this file in your &lt;code&gt;ava.config.js&lt;/code&gt; like below:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;default&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;extensions&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;ts&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;vue&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;require&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;./src/tests/_setup.js&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;babel&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;files&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;src/tests/**/*.spec.ts&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;At this point you should be able to run your Ava tests as intended and it would work just fine. However, if you want to have code coverage for the project, read on on how to setup your NYC config.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#nyc-optional&#34;&gt;
    &lt;h2 id=&#34;nyc-optional&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;NYC (optional)&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;One of the main reasons I started exploring for this option is to get good code coverage report. This setup allows us to let NYC generate proper coverage reports.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;instrument&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;sourceMaps&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;all&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;check-coverage&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;per-file&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;include&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;src/**/*.{ts,vue}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;exclude&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;**/*.{spec,test}.{js,ts}&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;src/scripts/**/*&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;src/tests/**/*&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;*.js&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;extension&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;.ts&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;.vue&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;a class=&#34;anchor&#34; href=&#34;#conclusion&#34;&gt;
    &lt;h1 id=&#34;conclusion&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Conclusion&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h1&gt;
&lt;/a&gt;
&lt;p&gt;Personally, I wrote this piece based on my work on &lt;a href=&#34;https://globetrotte.com&#34;&gt;GlobeTrotte&lt;/a&gt;. I&amp;rsquo;ve tried my best to cleanup the code for it to be as reusable as I can. Feel free to let me know if there&amp;rsquo;s any issue with it. Hopefully you find this helpful.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Cross Language Object Parsing</title>
      <link>https://binhong.me/blog/2020-08-09-cross-language-object-parsing/</link>
      <pubDate>Sun, 09 Aug 2020 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2020-08-09-cross-language-object-parsing/</guid>
      <description>&lt;p&gt;I was working on a side project where I had the backend and frontend setup to run on different programming languages. As I was working on it, it started to bug me a little that I have to write the same object / struct and enum for each language in use everytime I introduce a new enum or struct. It can also be confusing / dangerous if I updated one side and forget to update the other.&lt;/p&gt;
&lt;p&gt;At first, I just wrote some shell script to automate this but it quickly become unfeasible. (Clarification: it&amp;rsquo;s still possible to do this, but without any sort of object oriented support, the code gets quite messy quickly and I didn&amp;rsquo;t want to maintain that.) Now, at this point I should let you know that there are actually multiple existing solutions (Apache Thrift or Protobuf) that are way more matured, robust, and designed for use in a much larger scale. If you&amp;rsquo;re working on an enterprise level solution (or looking to learn something that&amp;rsquo;s more transferable into your future work), you might want to take look into them instead. I personally wanted something simpler and more flexible so I began working on &lt;a href=&#34;https://wings.sh&#34;&gt;wings&lt;/a&gt;.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#installation&#34;&gt;
    &lt;h2 id=&#34;installation&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Installation&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;If you have nimble (comes with nim) installed, you can install wings with &lt;code&gt;nimble install wings&lt;/code&gt;. As of now, the only alternative to that is to get the binary and place it in a folder included by the path (like &lt;code&gt;/usr/bin/&lt;/code&gt;) which unfortunately means that it won&amp;rsquo;t automatically update when a newer version is released. I&amp;rsquo;m still working on (and learning how to) deploy it to more package managers for easier installation process.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#usage&#34;&gt;
    &lt;h2 id=&#34;usage&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Usage&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;You write a wings file like the example below then run &lt;code&gt;wings &amp;lt;FILENAME&amp;gt;.wings&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;25
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;26
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;27
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;28
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;29
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;30
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;31
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;32
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;33
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;go-filepath examples/go/classroom
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;kt-filepath examples/kt
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;nim-filepath examples/nim
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;py-filepath examples/py
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ts-filepath examples/ts
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;py-import examples.output.py.people
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ts-import { IWingsStruct }:wings-ts-util
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;import examples/input/emotion.wings
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;import examples/input/homework.wings
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;py-implement People
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ts-implement IWingsStruct
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;# Any person who is studying in a class
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;struct Student {
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  id          int       -1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  name        str
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  cur_class   str
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  feeling     Emotion   Emotion.Meh
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  is_active   bool
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  year        date
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  graduation  date
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  homeworks   []Homework
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  something   Map&amp;lt;str,str&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ts-func(
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  public addHomework(hw: Homework): void {
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    this.Homeworks.push(hw);
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;As you can see, not only does it support generating files based on given specific parameters, it also supports importing other wings files directly which will also be generated by wings (unless specified otherwise in your config file).&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#library&#34;&gt;
    &lt;h2 id=&#34;library&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Library&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Since it&amp;rsquo;s very beginning, wings has been published on nimble as a library for nim. With this, you will be able to make further customizations by importing it as a library especially if you only want to use a specific part of it. From there, you can even use build tools to incorporate it as part of your workflow if supported. (Shameless plug, I wrote the PR for please&amp;rsquo;s basic &lt;a href=&#34;https://github.com/thought-machine/pleasings/pull/29&#34;&gt;nim&lt;/a&gt; and &lt;a href=&#34;https://github.com/thought-machine/pleasings/pull/31&#34;&gt;nimble&lt;/a&gt; support through pleasings. Though admittedly it isn&amp;rsquo;t quite as complete as you can see here I use &lt;a href=&#34;https://github.com/binhonglee/wings/blob/devel/build_defs/nim/nim.build_defs&#34;&gt;my own version of the config&lt;/a&gt; in the repo.)&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve also created &lt;a href=&#34;https://github.com/binhonglee/stones&#34;&gt;&lt;code&gt;stones&lt;/code&gt;&lt;/a&gt;, a separate library that contains some generic functions and tools used in wings.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#generated-files&#34;&gt;
    &lt;h2 id=&#34;generated-files&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Generated Files&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#struct&#34;&gt;
    &lt;h3 id=&#34;struct&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;&lt;a href=&#34;https://wings.sh/#struct&#34;&gt;Struct&lt;/a&gt;&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h3&gt;
&lt;/a&gt;
&lt;p&gt;Unlike the afromentioned existing alternatives, it doesn&amp;rsquo;t change the way the data was stored and transferred. You&amp;rsquo;ll still be converting them from and to JSON and communicate through JSON (so they will still be human readable). Therefore, comparatively, it will be &amp;ldquo;slower&amp;rdquo; than the alternatives as it will inherit the same &amp;ldquo;slowness&amp;rdquo; of JSON (depending on how it is serialized and deserialized in each individual languages) and bulkiness (it&amp;rsquo;s a pretty well established fact that JSON string takes more space in memory than something like a serialized thrift object).&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#enum&#34;&gt;
    &lt;h3 id=&#34;enum&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;&lt;a href=&#34;https://wings.sh/#enum&#34;&gt;Enum&lt;/a&gt;&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h3&gt;
&lt;/a&gt;
&lt;p&gt;Nothing much about them. They&amp;rsquo;re just normal enums that starts from 0 and increment by 1 for each element in it. Custom type and initialization is in the works and might change up the syntax a little when support is added.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#util-classes&#34;&gt;
    &lt;h2 id=&#34;util-classes&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Util Classes&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;While the generated files (generally) work out of the box, they by themselves are still lacking of certain features that can be applied across all Struct or Enum classes. This is where the util classes comes in. They are written in each of their respective targeted languages as a package / library. Currently, there&amp;rsquo;s only a Util class for TypeScript since golang already have all the features in the util class baked in with its JSON support.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#config&#34;&gt;
    &lt;h2 id=&#34;config&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;&lt;a href=&#34;https://wings.sh/config/&#34;&gt;Config&lt;/a&gt;&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;While the use of a config file is optional, its also where the core value &amp;ldquo;customizable&amp;rdquo; comes from. Config files are accpeted in JSON format and should be added as an argument when calling wings with &lt;code&gt;-c:&amp;lt;CONFIG.json&amp;gt;&lt;/code&gt;. Some of the more simple / straightforward configurable value includes things like &lt;a href=&#34;https://wings.sh/config/#logging-int&#34;&gt;&lt;code&gt;logging&lt;/code&gt;&lt;/a&gt; which controls the verbosity of the output message in the terminal, &lt;a href=&#34;https://wings.sh/config/#skipimport-bool&#34;&gt;&lt;code&gt;skipImport&lt;/code&gt;&lt;/a&gt; which lets you dictate if you&amp;rsquo;d want to also generate imported files or skip doing so, and &lt;a href=&#34;https://wings.sh/config/#header-str&#34;&gt;&lt;code&gt;header&lt;/code&gt;&lt;/a&gt; which lets you specify your customized the header comment for all the generated files. On top of that, you can also configure how you want the output files to be formatted by specifying your own templates in &lt;a href=&#34;https://wings.sh/config/#langconfigs-str&#34;&gt;&lt;code&gt;langConfig&lt;/code&gt;&lt;/a&gt; (more on this below).&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#template&#34;&gt;
    &lt;h2 id=&#34;template&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;&lt;a href=&#34;https://wings.sh/template/&#34;&gt;Template&lt;/a&gt;&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;As mentioned above, you can also write your own or import someone else&amp;rsquo;s templates (with &lt;a href=&#34;https://wings.sh/experimental/#remote-template&#34;&gt;&lt;code&gt;remoteLangConfigs&lt;/code&gt;&lt;/a&gt;) to configure the output files in your own format or even to output to languages that are not officially supported. This allows wings to be used in a more dynamic way both in terms of range of supported languages and style format instead of being limited by what&amp;rsquo;s officially supported (like in Thrift and protobuf though they do already have a wide range of support).&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#takeaways&#34;&gt;
    &lt;h2 id=&#34;takeaways&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Takeaways&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Overall, I think one of the main takeaway is that if there&amp;rsquo;s already an actively maintained existing solution, it&amp;rsquo;s probably better to use that than to create your own. By now you might have realizes that this project is slowly morphing into something that looks similar to the existing alternatives but in a much simpler form.&lt;/p&gt;
&lt;p&gt;Admittedly, I&amp;rsquo;ve only started learning about how a programming language is written only after a few months into the project so the design and structure is not ideal. However, since this is also much simpler compared to a full blown programming language, I can still get away with it on this. That said, I might start to incorporate some of these principles into wings as I continue to work on it (including writing a proper lexer and parser for it).&lt;/p&gt;
&lt;p&gt;Of course, this is just a little side project spawn off of another side project. There&amp;rsquo;s also the long time dilemma of &amp;ldquo;should I just write them manually or do I need to write codes (or use an external library) to write them&amp;rdquo; (the NIH syndrome). In this case the later took way more time than the former, but this is a side project so it&amp;rsquo;s more about learning than meeting non-existent delivery dates anyway. I&amp;rsquo;ve also find it fun to have been working on the project so far and I hope you find this helpful. If you&amp;rsquo;d like to learn more about it (or use it yourself), check out the project website at &lt;a href=&#34;https://wings.sh&#34;&gt;wings.sh&lt;/a&gt;. If you like what you&amp;rsquo;ve read, I&amp;rsquo;d appreciate a star on the &lt;a href=&#34;https://github.com/binhonglee/wings&#34;&gt;wings repository&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Multi-language Support Build Tool</title>
      <link>https://binhong.me/blog/2019-08-07-multi-language-support-build-tool/</link>
      <pubDate>Wed, 07 Aug 2019 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2019-08-07-multi-language-support-build-tool/</guid>
      <description>&lt;p&gt;I recently started learning about Go to find out what’s all the hype about. So I figured what’s a better way than to build a project out of it? As I was setting up my machine and going through tutorials step-by-step, I was slightly annoyed by the fact that the development of Go projects are limited to inside the &lt;code&gt;$GOPATH&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Personally, I have all my side projects stored at the top level of the computer. It might not be the best way to do it but I’m liking that I can see all my projects at one glance. I guess one could argue that you can soft link the folder into the folder inside &lt;code&gt;$GOPATH&lt;/code&gt; and I would pretty much achieve the same effect. But it didn’t quite feel right to me, sounds like a really hacky workaround that might not worth the maintenance cost as I switch to a separate machine only to have to do everything all over again.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;*Note: Yes, I did eventually found out that you can now do development without being in &lt;code&gt;$GOPATH&lt;/code&gt; with &lt;code&gt;go mod&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Instead, I started looking into build tools that can do that. Admittedly, I intentionally picked something that’s not so mainstream (Buck, Bazel, Pants) and found Please. (The other part of me liked the idea of naming my run alias as &lt;code&gt;plz work&lt;/code&gt; because it’s mildly funny.)&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#build-tools&#34;&gt;
    &lt;h2 id=&#34;build-tools&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Build Tools&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;So what is a build tool and why do we need them? In most occasion of a side project, you build and run each part of your project separately in a package of its own with only one (or two) programming languages each. In which you never needed to cross compile or compile multiple projects at once in parallel. Even when you do, there’s nothing that needs more than a couple lines of shell scripts to string them together since the performance gain through parallel compiling is not that significant if at all.&lt;/p&gt;
&lt;p&gt;However, this does not hold the same for companies that adopt the concept of monorepo. Instead of multiple smaller repositories, they host all of their code (which can be a mix of many different languages and requires many different build flows) in just a single repositories. In this case, different projects can also share the same library that is already imported and in use by the other project thus making sure that the imported library version is always consistent across all the projects.&lt;/p&gt;
&lt;p&gt;Here’s a quick summary of why and why not. I’m not here to debate if you should or should not use a build tool or monorepo. I’ll leave them to the experts. I’m just here to share about the possibility of doing so.&lt;/p&gt;
&lt;p&gt;Advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Multi-language compatibility&lt;/li&gt;
&lt;li&gt;Not limited to any specific path&lt;/li&gt;
&lt;li&gt;Consistently work across supported devices&lt;/li&gt;
&lt;li&gt;Smoother e2e workflow (if set up correctly)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Disadvantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Slightly steeper learning curve&lt;/li&gt;
&lt;li&gt;Can be complicated to set them up&lt;/li&gt;
&lt;/ul&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#installation&#34;&gt;
    &lt;h2 id=&#34;installation&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Installation&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;There are a few pretty straightforward way to install it according to their &lt;a href=&#34;https://please.build/quickstart.html&#34;&gt;documentation&lt;/a&gt; though admittedly, you probably shouldn’t just run some random script from some random website online (but then again, compiling from source can be a bit of a pain) so that’s really up to you.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;*Note: You mostly only need this installed to initialize the project. Others wanting to build / develop the project does not necessarily have to have &lt;code&gt;please&lt;/code&gt; installed locally for them. Similar to how Gradle works with &lt;code&gt;./gradlew&lt;/code&gt; on machines without Gradle installed, Please generates a &lt;code&gt;./pleasew&lt;/code&gt; file that can be ran as an alternative if one does not have Please installed in the machine.&lt;/em&gt;&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#setup&#34;&gt;
    &lt;h2 id=&#34;setup&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Setup&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#build-files&#34;&gt;
    &lt;h3 id=&#34;build-files&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;BUILD files&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h3&gt;
&lt;/a&gt;
&lt;p&gt;Honestly the BUILD file syntax for Please looks very much alike (if not identical) to some of the other more popular build tools like Bazel or Buck. I don’t know of the exact name for it but it is very Python-ish. In fact, GitHub linguist’s &lt;code&gt;language.yml&lt;/code&gt; actually explicitly &lt;a href=&#34;https://github.com/github/linguist/blob/master/lib/linguist/languages.yml#L4001-L4003&#34;&gt;classifies them as Python&lt;/a&gt;. I recommend checking out &lt;a href=&#34;https://please.build/lexicon.html&#34;&gt;the documentation&lt;/a&gt; as they are pretty good in detailing the available fields and their purpose for it.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#external-dependencies&#34;&gt;
    &lt;h3 id=&#34;external-dependencies&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;External Dependencies&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h3&gt;
&lt;/a&gt;
&lt;p&gt;This is probably the most tedious part of the whole process. It makes you realize how much have you’ve taken tools like &lt;code&gt;dep&lt;/code&gt; or even &lt;code&gt;npm&lt;/code&gt; and &lt;code&gt;yarn&lt;/code&gt; for granted. Unlike those other tools I’ve mentioned, you’ll have to manually list each and every one of the external dependencies (and their dependencies) properly so Please knows which to prioritize and how to build and manage them in the most efficient parallel way. Fortunately, this should be a one time thing as even if you consistently increase dependencies, chances are you already have the dependencies of the new dependencies listed.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#code-structure&#34;&gt;
    &lt;h2 id=&#34;code-structure&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Code Structure&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;You have a lot of freedom here. While you might not want to go crazy and have your code all over the place (making them referencing each other a nightmare), you can afford to structure them slightly different from the conventional way since the build tool will handle the hierarchical setup for you. For instance, you can have your main go file 2 levels (or more) inside some folder unlike the usual top level requirements. Similarly, if you need some folders of multiple different programming languages to overlap in between each other, that’s pretty possible (though I would question the need for it and if there’s a rational reasoning behind it).&lt;/p&gt;
&lt;hr&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#pleasings&#34;&gt;
    &lt;h2 id=&#34;pleasings&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;pleasings&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Aside from the built-in supported languages and rules, they also have an additional repository that is home to the rules that are not actively maintained / updated thus not being part of the core functionality / rule. These includes support for things like Android, Rust, Scala, Kotlin and even Nim (okay, I wrote the one for Nim so I’m just shamelessly sneaking it in here).&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#genrule--gentest&#34;&gt;
    &lt;h2 id=&#34;genrule--gentest&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;genrule() / gentest()&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Even with pleasings, there’s a possibility where there’s something you work on (or need for your project) that is not supported. In my project, I’m also compiling Vue.js code for the web. In this case, I used a genrule() to build it with pnpm (similar to npm and yarn) and a gentest() to run its tests. While it’s pretty straightforward to do it this way, I’ve also effectively reduced the benefits of using a build tool since I’m mostly waiting on this one rule (and rely on pnpm’s build efficiency instead of Please’s) when building my entire project. Here is an example how I did mine:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-py&#34; data-lang=&#34;py&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;genrule&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;name&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;pnpm&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;visibility&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;PUBLIC&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;outs&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;node_modules&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;cmd&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39; &amp;amp;&amp;amp; &amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;join&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;([&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s2&#34;&gt;&amp;#34;top_level=$(pwd | awk -F&amp;#39;plz-out&amp;#39; &amp;#39;{print $1}&amp;#39;)&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s2&#34;&gt;&amp;#34;pnpm i&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s2&#34;&gt;&amp;#34;ln -s &lt;/span&gt;&lt;span class=&#34;se&#34;&gt;\&amp;#34;&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;$top_level&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;\&amp;#34;\&amp;#34;&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;node_modules&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;\&amp;#34;&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt; &lt;/span&gt;&lt;span class=&#34;se&#34;&gt;\&amp;#34;&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;node_modules&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;\&amp;#34;&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;]),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;genrule&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;name&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;build&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;outs&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;dist&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;cmd&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39; &amp;amp;&amp;amp; &amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;join&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;([&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s2&#34;&gt;&amp;#34;current=$(pwd)&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s2&#34;&gt;&amp;#34;cd $(pwd | awk -F&amp;#39;plz-out&amp;#39; &amp;#39;{print $1}&amp;#39;)&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s2&#34;&gt;&amp;#34;pnpm run build&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s2&#34;&gt;&amp;#34;mv &lt;/span&gt;&lt;span class=&#34;se&#34;&gt;\&amp;#34;&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;dist&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;\&amp;#34;&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt; &lt;/span&gt;&lt;span class=&#34;se&#34;&gt;\&amp;#34;&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;$current&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;\&amp;#34;&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;]),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;deps&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;:pnpm&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;*Note: Please do have &lt;a href=&#34;https://github.com/thought-machine/pleasings/tree/master/js&#34;&gt;some support for JavaScript&lt;/a&gt; in the &lt;code&gt;pleasings&lt;/code&gt; repository in which you can use their &lt;code&gt;yarn_library()&lt;/code&gt; and &lt;code&gt;js_binary()&lt;/code&gt;. However due to the nature of node.js dependency hell nowadays, handwriting the &lt;code&gt;yarn_library()&lt;/code&gt; rules for all the libraries you want + all the libraries those libraries depends on and so forth will probably take forever. I also did not look too deep into this to tell if this would work any differently from just running &lt;code&gt;yarn install&lt;/code&gt; just by itself.&lt;/p&gt;
&lt;hr&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#ci-testing&#34;&gt;
    &lt;h2 id=&#34;ci-testing&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;CI Testing&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;One of the other benefit of having a build tool properly set up is that it would be slightly easier (and more consistent) to set up your CI testing both in terms of testing environment and testing outcome. Of course, there are still some external dependencies like language version installed etc but for the most part, build tools would always pull fresh version of the new build regardless if there is a cached version on the machine (that might mess with the consistency of the test result).&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#vscode&#34;&gt;
    &lt;h2 id=&#34;vscode&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;VSCode&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;For most code editors, their linter will rely on the env value of &lt;code&gt;$GOPATH&lt;/code&gt; to look for third party libraries used in your go codes. Since those libraries don’t live in there when building with Please (it is in the &lt;code&gt;plz-out&lt;/code&gt; folder inside your project folder), you will want to have a &lt;code&gt;settings.json&lt;/code&gt; file that points the linter to the right location consisting of the compiled libraries. Something like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;go.inferGopath&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;go.gopath&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;{workplaceFolder}/plz-out/go:/{workplaceFolder}/plz-out/go/src&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;files.watcherExclude&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nt&#34;&gt;&amp;#34;**/plz-out/**&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;em&gt;*Note: you will want to replace &lt;code&gt;{workplaceFolder}&lt;/code&gt; with an absolute path to wherever your project lives since the linter seems to complain about using a relative path as &lt;code&gt;$GOPATH&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Hopefully this has been helpful in opening you up to trying and mixing different programming languages into your project. Why? To me, side projects are just experimental grounds for fun. If it sounds interesting enough, I’ll try and make it happen (or in this case, see if someone has done it and use their ready build tool).&lt;/p&gt;
&lt;p&gt;If you are interested, &lt;a href=&#34;https://github.com/binhonglee/GlobeTrotte/&#34;&gt;this&lt;/a&gt; is the side project I’ve been working on. Thus far, its still not functional. It will most likely take another few months or so (depending on my progress and how much free time I have committed to this outside of work) before I can deploy a minimally working version of it.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Functional and flexible shell scripting tricks</title>
      <link>https://binhong.me/blog/2019-05-07-shell-scripting/</link>
      <pubDate>Tue, 07 May 2019 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2019-05-07-shell-scripting/</guid>
      <description>&lt;p&gt;Its 2019 now, who writes shell scripts anymore? Am I right? Well, apparently I do. ¯\_(ツ)_/¯&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#shell-scripts-vs-python-or-perl&#34;&gt;
    &lt;h2 id=&#34;shell-scripts-vs-python-or-perl&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Shell scripts vs python or perl&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;There are some good arguments for that &lt;a href=&#34;https://stackoverflow.com/questions/796319/strengths-of-shell-scripting-compared-to-python#796343&#34;&gt;here&lt;/a&gt; and &lt;a href=&#34;https://www.linuxquestions.org/questions/linux-newbie-8/what-is-the-difference-between-perl-and-shell-scripting-4175486499/&#34;&gt;here&lt;/a&gt; which mainly revolve around 2 things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Shell exists in all Unix systems and makes use of system default features.&lt;/li&gt;
&lt;li&gt;Shell is an &amp;ldquo;interactive command function&amp;rdquo; designed to get user inputs during the process of running them.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Also, &lt;a href=&#34;https://stackoverflow.com/questions/5725296/difference-between-sh-and-bash&#34;&gt;here&lt;/a&gt;&amp;rsquo;s an additional relevant reading about the differences between &lt;code&gt;sh&lt;/code&gt; and &lt;code&gt;bash&lt;/code&gt;.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#arguments&#34;&gt;
    &lt;h2 id=&#34;arguments&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Arguments&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;In some occasions, you will need to pass an argument (or expect one) into the script like how you might pass a param into a function. In that case, you will use something like &lt;code&gt;$1&lt;/code&gt; for the first argument, &lt;code&gt;$2&lt;/code&gt; for the second. Here&amp;rsquo;s an example of how it would look like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;The input message was &lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$1&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;./run_this.sh userInput
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;The input message was userInput.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;em&gt;Note: The params are separated by spaces so if you want to input a string as a param that contains a space, it might need to be something like &lt;code&gt;./run_this.sh &amp;quot;user input&amp;quot;&lt;/code&gt; just so &lt;code&gt;&amp;quot;user input&amp;quot;&lt;/code&gt; would be counted as &lt;code&gt;$1&lt;/code&gt; entirely.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In the occasion where you are not sure how long the user input might be and you want to capture it all, you would use &lt;code&gt;$@&lt;/code&gt; instead. In the following example, I took in the entire string and print them out word by word after breaking them into a string array according to the spaces in between.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;userInputs&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$@&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; i in &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;userInputs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[@]&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$i&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;./run_this.sh who knows how long this can go
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;who
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;knows
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;how
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;long
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;this
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;can
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;go
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;a class=&#34;anchor&#34; href=&#34;#functions&#34;&gt;
    &lt;h2 id=&#34;functions&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Functions&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;If you have done any sort of programming, you should be familiar with the concept of &lt;em&gt;functions&lt;/em&gt;. It&amp;rsquo;s basically a set of commands / operations that you will be repeating over and over again. Instead of repeating it multiple times in your code, you can put them into a function. Then just call the function which effectively reduces the lines of code that need to be written.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Side note: If you don&amp;rsquo;t know already, LOC is a horrible metric for any sort of measurement in terms of programming. Don&amp;rsquo;t take this from me, take this from &lt;a href=&#34;https://www.goodreads.com/quotes/536587-measuring-programming-progress-by-lines-of-code-is-like-measuring&#34;&gt;Bill Gates&lt;/a&gt;:&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&amp;ldquo;Measuring programming progress by lines of code is like measuring aircraft building progress by weight.&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here&amp;rsquo;s how a normal function looks like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Declaring the function&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;doSomething&lt;span class=&#34;o&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Calling the function&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;doSomething
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Pretty straightforward and easy to understand. Now, here&amp;rsquo;s a few differences between functions in shell scripts and a normal programming language.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#parameters&#34;&gt;
    &lt;h3 id=&#34;parameters&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Parameters&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h3&gt;
&lt;/a&gt;
&lt;p&gt;If you were to pass a parameter / use a parameter into a function in Java, you have to declare them in the function declaration. They look something like this.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;public&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;static&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;main&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;[]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;doSomething&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;random String&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;private&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;static&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;void&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;doSomething&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;words&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;System&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;out&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;println&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;words&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In the shell, however, they do not require a declaration of types or names at all. Each of them is like a separate script that lives in the script itself. If you were to use a param, just pass it in and call it like how you would do it if you were taking in input for this script at the top level. Something like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;doSomething&lt;span class=&#34;o&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;doSomething &lt;span class=&#34;s2&#34;&gt;&amp;#34;random String&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;Similar to above, if you want to take in everything, you will use &lt;code&gt;$@&lt;/code&gt; instead of &lt;code&gt;$1&lt;/code&gt; since &lt;code&gt;$1&lt;/code&gt; would only use the first input (and &lt;code&gt;$2&lt;/code&gt; for the second etc.).&lt;/li&gt;
&lt;li&gt;Functions need to be declared ahead of where they are being called. (Usually beginning of the file before any main operations.)&lt;/li&gt;
&lt;/ol&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#return&#34;&gt;
    &lt;h3 id=&#34;return&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Return&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h3&gt;
&lt;/a&gt;
&lt;p&gt;Let&amp;rsquo;s say we create a script like below named &lt;code&gt;run_this.sh&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;doSomething&lt;span class=&#34;o&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;magic&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;output&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;doSomething&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$output&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Now let&amp;rsquo;s run it and see what is being assigned to the &lt;code&gt;output&lt;/code&gt; variable.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ ./run_this.sh
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;magic
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Note that instead of &lt;code&gt;0&lt;/code&gt;, it shows &lt;code&gt;magic&lt;/code&gt; instead. This is because when you do &lt;code&gt;output=`doSomething` &lt;/code&gt;, it assigns the output message to &lt;code&gt;output&lt;/code&gt; instead of the return value since output message is how you communicate almost anything in shell script.&lt;/p&gt;
&lt;p&gt;So when does it make sense to use the &lt;code&gt;return&lt;/code&gt; call then? When you are using it as part of an if statement. Something like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;doSomething&lt;span class=&#34;o&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;magic&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; doSomething&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Its true!&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-txt&#34; data-lang=&#34;txt&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ ./run_this.sh
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Its true!
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In this case, &lt;code&gt;return 0&lt;/code&gt; means &lt;code&gt;true&lt;/code&gt; while &lt;code&gt;return 1&lt;/code&gt; meant &lt;code&gt;false&lt;/code&gt; in a traditional &lt;code&gt;boolean&lt;/code&gt; sense.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#multi-line-echo&#34;&gt;
    &lt;h2 id=&#34;multi-line-echo&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Multi-line echo&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;There are times when you need to print a multi-line message. There are a few ways to go around this. The easiest way is to use &lt;code&gt;echo&lt;/code&gt; multiple times like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;line1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;line2&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;line3&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;It works but probably not the most elegant way to get around this. Instead, you can use &lt;code&gt;cat &amp;lt;&amp;lt; EOF&lt;/code&gt; instead. Something like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cat &lt;span class=&#34;s&#34;&gt;&amp;lt;&amp;lt; EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;line1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;line2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;line3
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;EOF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Note that there should not be anything (including spaces or tabs) before &lt;code&gt;EOF&lt;/code&gt;. If you want to do it in an &lt;code&gt;if&lt;/code&gt; statement, it should look something like this.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;a&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;a&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  cat &lt;span class=&#34;s&#34;&gt;&amp;lt;&amp;lt; EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;line1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;line2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;line3
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s&#34;&gt;EOF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Realize that even the messages itself are aligned to the left. This is because if you leave them tabbed, the output message shown in the command line will also be tabbed. Also, if &lt;code&gt;EOF&lt;/code&gt; is tabbed, shell would complain about it and usually ends the script there.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#flags--options&#34;&gt;
    &lt;h2 id=&#34;flags--options&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Flags / Options&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;You&amp;rsquo;ve probably seen some of the scripts or commands that comes with an ability to add flags (and sometimes arguments for the specific flag). Something like &lt;code&gt;git commit -a -m &amp;quot;Some commit message&amp;quot;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a quick example of how it looks like (I&amp;rsquo;ve tried to be as comprehensive as possible with the example.)&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;while&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;getopts&lt;/span&gt; ac: opt&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;case&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$opt&lt;/span&gt; in
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        a&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;\&amp;#34;a\&amp;#34; was executed.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;p&#34;&gt;;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        c&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;\&amp;#34;c\&amp;#34; was executed with parameter \&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$OPTARG&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;\&amp;#34;.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;p&#34;&gt;;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;se&#34;&gt;\?&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Invalid option: -&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$opt&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;nb&#34;&gt;exit&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;p&#34;&gt;;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        :&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;option -&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$opt&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt; requires an argument.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;nb&#34;&gt;exit&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            &lt;span class=&#34;p&#34;&gt;;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;esac&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ ./run_this.sh
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ ./run_this.sh -a
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&amp;#34;a&amp;#34; was executed.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ ./run_this.sh -c
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;option -c requires an argument.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ ./run_this.sh -c abcd
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&amp;#34;c&amp;#34; was executed with parameter &amp;#34;abcd&amp;#34;.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ ./run_this.sh -a -c abc
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&amp;#34;a&amp;#34; was executed.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&amp;#34;c&amp;#34; was executed with parameter &amp;#34;abc&amp;#34;.
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ ./run_this.sh -x
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Invalid option: -x
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In the above example, the differences between option &lt;code&gt;-a&lt;/code&gt; and &lt;code&gt;-c&lt;/code&gt; is that in the &lt;code&gt;getopts&lt;/code&gt; line, &lt;code&gt;c&lt;/code&gt; has a colon (&lt;code&gt;:&lt;/code&gt;) following it after therefore telling the program to expect a parameter for the option. Another thing to keep in mind is that the options need to be declared in an alphabetical way. If you declare something like &lt;code&gt;acb&lt;/code&gt;, the &lt;code&gt;b&lt;/code&gt; declaration would be ignored and using the &lt;code&gt;-b&lt;/code&gt; flag would lead to the error message instead of the &lt;code&gt;b&lt;/code&gt; case in the switch condition.&lt;/p&gt;
&lt;p&gt;Thanks for reading!&lt;/p&gt;
&lt;hr&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#references&#34;&gt;
    &lt;h2 id=&#34;references&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;References&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://wiki.bash-hackers.org/howto/getopts_tutorial&#34;&gt;Small getopts tutorial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://stackoverflow.com/questions/10969953/how-to-output-a-multiline-string-in-bash#10970616&#34;&gt;How to output a multiline string in Bash&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href=&#34;https://medium.freecodecamp.org/functional-and-flexible-shell-scripting-tricks-a2d693be2dd4&#34;&gt;freeCodeCamp&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
    <item>
      <title>Test Driven Development with Alexa SDK</title>
      <link>https://binhong.me/blog/2018-10-29-test-driven-development-with-alexa-sdk/</link>
      <pubDate>Mon, 29 Oct 2018 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2018-10-29-test-driven-development-with-alexa-sdk/</guid>
      <description>&lt;a class=&#34;anchor&#34; href=&#34;#what-is-test-driven-development&#34;&gt;
    &lt;h1 id=&#34;what-is-test-driven-development&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;What is Test Driven Development?&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h1&gt;
&lt;/a&gt;
&lt;p&gt;According to &lt;a href=&#34;https://en.wikipedia.org/wiki/Test-driven_development&#34;&gt;Wikipedia&lt;/a&gt;, it means “Requirements are turned into very specific &lt;a href=&#34;https://en.wikipedia.org/wiki/Test_case&#34;&gt;test cases&lt;/a&gt;, then the software is improved to pass the new tests, only.” Basically, you would first have to write the test of the software/feature you are developing before you start working on the developing the software/feature itself.&lt;/p&gt;
&lt;p&gt;Here are a few more articles on the benefits and why you should apply TDD:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/mobile-quality/test-driven-development-d16fd216d45c&#34;&gt;Test Driven Development&lt;/a&gt; by Jan Olbrich&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://hackernoon.com/test-driven-development-with-alexa-sdk-777f6b5e5486&#34;&gt;Isn’t TDD twice the work? Why should you care?&lt;/a&gt; by Navdeep Singh&lt;/li&gt;
&lt;/ul&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#preparation&#34;&gt;
    &lt;h1 id=&#34;preparation&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Preparation&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h1&gt;
&lt;/a&gt;
&lt;p&gt;Here are a few thing you might want to have set up or installed before getting started:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://aws.amazon.com/&#34;&gt;Amazon Web Service&lt;/a&gt; account (a free one will work just fine)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://nodejs.org/en/&#34;&gt;node.js&lt;/a&gt; (personally though, I prefer installing and maintaining it through &lt;a href=&#34;https://github.com/creationix/nvm&#34;&gt;nvm&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Text editor of your choice&lt;/li&gt;
&lt;/ul&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#setting-up&#34;&gt;
    &lt;h1 id=&#34;setting-up&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Setting up&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h1&gt;
&lt;/a&gt;
&lt;ol&gt;
&lt;li&gt;Create a new project folder (&lt;code&gt;mkdir project-name&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Navigate into that new folder (&lt;code&gt;cd project-name&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Initialize npm (&lt;code&gt;npm init&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Follow through everything as you prefer (if you don’t know what it is, just press enter and it’ll keep the default) except for test command:, put in node test/testflow.js instead. The command line should look something like the snippet below.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;This&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;utility&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;will&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;walk&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;you&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;through&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;creating&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kn&#34;&gt;package&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;json&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;file&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;It&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;only&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;covers&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;the&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;most&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;common&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;items&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;and&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;tries&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;to&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;guess&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sensible&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;defaults&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;See&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;`npm help json`&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;definitive&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;documentation&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;on&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;these&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;fields&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;and&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;exactly&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;what&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;they&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;do&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;Use&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;`npm install &amp;lt;pkg&amp;gt;`&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;afterwards&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;to&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;install&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kn&#34;&gt;package&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;and&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;save&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;it&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;as&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;dependency&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;in&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;the&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kn&#34;&gt;package&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;json&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;file&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;Press&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;^&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;C&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;at&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;any&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;time&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;to&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;quit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;package&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;emptyproject&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;version&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mf&#34;&gt;1.0.0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;description&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;entry&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;point&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;index&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;js&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;test&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;command&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;node&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;test&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;/&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;testflow&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;js&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;git&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;repository&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;keywords&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;author&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;license&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ISC&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Now you have node.js setup for the project.&lt;/p&gt;
&lt;p&gt;Unit testing
Shout out to &lt;a href=&#34;https://twitter.com/robmccauley&#34;&gt;robmccauley&lt;/a&gt; for maintaining &lt;a href=&#34;https://github.com/robm26/testflow&#34;&gt;testflow&lt;/a&gt; that provides unit testing for Alexa skills. Here’s what the following commands will do :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Clone testflow repository into your project at the ‘test’ folder. (If this project is also a git project itself, I recommend doing &lt;code&gt;git submodule add&lt;/code&gt; instead of &lt;code&gt;git clone&lt;/code&gt;.)&lt;/li&gt;
&lt;li&gt;Create a folder named ‘dialogs’&lt;/li&gt;
&lt;li&gt;Create a file named ‘default.txt’ in the ‘dialog’ folder&lt;/li&gt;
&lt;li&gt;Edit the ‘SourceCodeFile’ line in the cloned ‘testflow’ repository to point to the ‘index.js’ in the base level of the folder (feel free to edit it to your personal preference).&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git clone git@github.com:robm26/testflow.git &lt;span class=&#34;nb&#34;&gt;test&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;mkdir dialogs
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;touch dialogs/default.txt
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sed -i &lt;span class=&#34;s1&#34;&gt;&amp;#39;9c const SourceCodeFile = &amp;#34;../index.js&amp;#34;&amp;#39;&lt;/span&gt; test/testflow.js
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Inside the ‘default.txt’ file in the ‘dialogs’ folder, you should have the intents you are trying to test. The default in the testflow repository consist of the followings but you should definitely add some more customized ones to it depending on what you are trying to do in your skill.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;LaunchRequest
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;AMAZON.HelpIntent
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;AMAZON.StopIntent
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In case where you are taking in variables through utterances, you can do something like this :&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;DoSomething Input1=userInput1 Input2=userInput2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;To get a deeper understanding into using testflow, I recommend looking into its &lt;a href=&#34;https://github.com/robm26/testflow/blob/master/tutorial/TUTORIAL.md&#34;&gt;tutorial page&lt;/a&gt; and the &lt;a href=&#34;https://developer.amazon.com/blogs/alexa/post/35eb8ae8-2cd8-4de7-86c5-97a1abc239b9/testflow-simulate-conversations-with-your-alexa-skill-code-to-ease-debugging&#34;&gt;blog post&lt;/a&gt; by the author himself.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#developing-the-skill&#34;&gt;
    &lt;h1 id=&#34;developing-the-skill&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Developing the skill&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h1&gt;
&lt;/a&gt;
&lt;p&gt;To be honest, I don’t have too much to talk about on this topic. Here is some sample code of how a sample skill can look like. (Also remember to do &lt;code&gt;npm install --save alexa-sdk&lt;/code&gt;.)&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;25
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;26
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;27
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;28
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;29
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;30
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;31
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;32
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;33
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;34
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;35
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;36
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;37
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;38
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;39
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;40
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;Alexa&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;require&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;alexa-sdk&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;exports&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;handler&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;event&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;callback&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;alexa&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;Alexa&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;handler&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;event&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;context&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;alexa&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;registerHandlers&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;handlers&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;alexa&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;execute&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;handlers&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;s1&#34;&gt;&amp;#39;LaunchRequest&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;emit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;:ask&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;Welcome to the sample skill. Say help for help.&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;s1&#34;&gt;&amp;#39;Intent1&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;emit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;:ask&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;This is the first intent.&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;s1&#34;&gt;&amp;#39;Intent2&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;input1&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;event&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;intent&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;slots&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;InputOne&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;toString&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;input2&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;event&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;intent&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;slots&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;InputTwo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;toString&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;response&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;doSomething&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;input1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;input2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;emit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;:ask&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;response&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;s1&#34;&gt;&amp;#39;AMAZON.HelpIntent&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;emit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;:ask&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;Insert some help message here.&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;s1&#34;&gt;&amp;#39;AMAZON.StopIntent&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;this&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;emit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;:tell&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;Have a good rest of your day!&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;doSomething&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;input1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;input2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;input1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;localeCompare&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;input2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;They are the same string!&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;They are different!&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;I recommend looking into the official &lt;a href=&#34;https://github.com/alexa/alexa-cookbook&#34;&gt;Alexa GitHub repository&lt;/a&gt; that provides all kinds of sample skills that you can work on top of.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#deployment&#34;&gt;
    &lt;h1 id=&#34;deployment&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Deployment&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h1&gt;
&lt;/a&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#alexa-skill-kit&#34;&gt;
    &lt;h2 id=&#34;alexa-skill-kit&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Alexa Skill Kit&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Go to &lt;a href=&#34;https://developer.amazon.com/alexa/console/&#34;&gt;this page&lt;/a&gt; and select ‘Create Skill’. In there you should see options to ‘add intents’ and ‘custom slot types’. You should add the appropriate slot types and intents accordingly from above. After you are done with that, select ‘Endpoint’ from the side bar and select ‘AWS Lambda ARN’ as the skill’s service endpoint. Make a copy of the Skill ID as you will need it later.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#aws-lambda&#34;&gt;
    &lt;h2 id=&#34;aws-lambda&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;AWS Lambda&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Create an AWS Lambda function selecting ‘from scratch’. After that, you should have an option to ‘add trigger’ where you would select ‘Alexa Skills Kit’. Now in the configuration dialog, you should put in the Skill ID you took a copy of previously then select ‘Add’.&lt;/p&gt;
&lt;p&gt;After this, select back to the box that labeled your project name. There should be a ‘Function code’ dialog box appeared below. While you can definitely copy and paste your code onto the online text editor, there might be chance where the packages you have installed and used in your code is included by default. It is also possible that you have more than 1 JavaScript files for the skill.&lt;/p&gt;
&lt;p&gt;In this case, you would select ‘Upload a .ZIP file’ under ‘Code entry type’. Locally, zip up all the JavaScript files along with the &lt;code&gt;node_modules&lt;/code&gt; folder then upload the resulting zip file.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#submission&#34;&gt;
    &lt;h2 id=&#34;submission&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Submission&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;When you are done, you can go back to the Alexa Skill Kit page, select the ‘Distribution’ tab and fill out the appropriate information before submitting your skill for review. Hopefully everything goes well and your skill gets published at the Alexa Skill Store!&lt;/p&gt;
&lt;hr&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#credits&#34;&gt;
    &lt;h2 id=&#34;credits&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Credits&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.amazon.com/docs/ask-overviews/build-skills-with-the-alexa-skills-kit.html&#34;&gt;Build Skills with the Alexa Skills Kit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.amazon.com/blogs/alexa/post/35eb8ae8-2cd8-4de7-86c5-97a1abc239b9/testflow-simulate-conversations-with-your-alexa-skill-code-to-ease-debugging&#34;&gt;TestFlow: Simulate Conversations with Your Alexa Skill Code to Ease Debugging&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href=&#34;https://hackernoon.com/test-driven-development-with-alexa-sdk-777f6b5e5486&#34;&gt;Hackernoon&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
    <item>
      <title>How to host multiple domain names and projects on one server</title>
      <link>https://binhong.me/blog/2018-08-29-how-to-host-multiple-domain-names-and-projects-on-one-server/</link>
      <pubDate>Wed, 29 Aug 2018 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2018-08-29-how-to-host-multiple-domain-names-and-projects-on-one-server/</guid>
      <description>&lt;p&gt;I own multiple domain names, and each one hosts a different side project. For the longest time, everything that required ‘hosting’ was hosted on Heroku. But their free tier can be quite limited, it can also get costly quickly if you are paying for each separate project. So instead, I decided to explore putting all of them together using NGINX (recommended to me by &lt;a href=&#34;https://jmw.fyi&#34;&gt;Jane Manchun Wong&lt;/a&gt;).&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#required-resources&#34;&gt;
    &lt;h2 id=&#34;required-resources&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Required Resources&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#virtual-private-server-vps&#34;&gt;
    &lt;h4 id=&#34;virtual-private-server-vps&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Virtual Private Server (VPS)&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h4&gt;
&lt;/a&gt;
&lt;p&gt;You’ll need a virtual server such as DigitalOcean or EC2 by AWS. Personally I uses &lt;a href=&#34;https://www.vultr.com/?ref=7358373&#34;&gt;Vultr&lt;/a&gt; (here’s the &lt;a href=&#34;http://vultr.com/&#34;&gt;non-referral link&lt;/a&gt;) which costs me about $2.50 / month.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#domain-names&#34;&gt;
    &lt;h4 id=&#34;domain-names&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Domain Names&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h4&gt;
&lt;/a&gt;
&lt;p&gt;You will need to register a few domain names. Assuming that you probably already have them, make sure your domain names are pointing at the name servers of your VPS. There should be a DNS section in your domain name service dashboard where you can select “custom DNS” or something similar. If you are not sure what the nameservers of your VPS are, you should be able to find that info easily through a simple search of “nameserver” + VPS service name.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#setting-up-nginx&#34;&gt;
    &lt;h2 id=&#34;setting-up-nginx&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Setting up NGINX&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#installation-and-basic-setup&#34;&gt;
    &lt;h4 id=&#34;installation-and-basic-setup&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Installation and basic setup&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h4&gt;
&lt;/a&gt;
&lt;p&gt;&lt;em&gt;Reference from &lt;a href=&#34;https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-16-04&#34;&gt;How To Install Nginx on Ubuntu 16.04&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Run the following commands through SSH-ing into the VPS. It will install NGINX, set firewall rules allowing it, and set NGINX to autostart on boot.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo apt-get update
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo apt-get install nginx
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo ufw allow &lt;span class=&#34;s1&#34;&gt;&amp;#39;Nginx HTTP&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo systemctl &lt;span class=&#34;nb&#34;&gt;enable&lt;/span&gt; nginx
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;a class=&#34;anchor&#34; href=&#34;#configuration-setup&#34;&gt;
    &lt;h4 id=&#34;configuration-setup&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Configuration setup&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h4&gt;
&lt;/a&gt;
&lt;p&gt;&lt;em&gt;Reference from &lt;a href=&#34;https://geekflare.com/multiple-domains-on-one-server-with-apache-nginx/&#34;&gt;Host Multiple Domains on One Server/IP with Apache or nginx&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The default virtual.conf location should be at /etc/nginx/conf.d/virtual.conf. I recommend backing up the default file before making any changes. (If it doesn’t exist, you can just create it.) Edit the file to look something like the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;25
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;26
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;27
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;28
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;29
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-nginx&#34; data-lang=&#34;nginx&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;server&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;listen&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;80&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;root&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;/opt/htdocs/binhong&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;index&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;index.html&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;index.htm&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;server_name&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;binhong.me&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;location&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;/&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kn&#34;&gt;try_files&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$uri&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$uri/&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;404&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;server&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;listen&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;80&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;root&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;/opt/htdocs/breakups&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;index&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;index.html&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;index.htm&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;server_name&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;breakups.life&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;location&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;/&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kn&#34;&gt;try_files&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$uri&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$uri/&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;404&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;server&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;listen&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;80&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;root&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;/opt/htdocs/breakupsAPI&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;index&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;index.html&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;index.htm&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;server_name&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;api.breakups.life&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;location&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;/&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kn&#34;&gt;proxy_redirect&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;http://127.0.0.1:8080/&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Here are a few things to look at:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;server&lt;/em&gt; block — Each of these should represent each different domain or subdomain in use.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;root&lt;/em&gt; — This is the location where the (HTML) files are loaded from.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;server_name&lt;/em&gt; — (sub)domain name(s) that should load these specific files.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;proxy_redirect&lt;/em&gt; — in cases where you are redirecting a specific subdomain to an active server, you will want to add this and put the IP location after it. (For local servers, either &lt;em&gt;http://127.0.0.1:port&lt;/em&gt; or &lt;em&gt;http://localhost:port&lt;/em&gt; should work as intended.)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo systemctl restart nginx
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;After you are done, restart the server so the new configurations will be loaded and applied.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#cloning-and-linking&#34;&gt;
    &lt;h2 id=&#34;cloning-and-linking&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Cloning and linking&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Now remember, since you have your directory pointing at /opt/htdocs/&lt;em&gt;websiteName&lt;/em&gt;, your initial thought might be to clone your projects into these folders. This can work, but it’s not ideal since many operations in these folders require root access to really do anything.&lt;/p&gt;
&lt;p&gt;Instead, you can clone them into your user folder or anywhere else like you normally would, and then create a soft link to connect the path to your repository folder. Something like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git clone git@github.com:binhonglee/binhonglee.github.io ~/website
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo ln -s ~/website /opt/htdocs/binhong
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Of course, when you are cloning a Node.js static site folder (ReactJS, Angular or Vue.js), you will want to install (&lt;code&gt;npm install&lt;/code&gt;) and build (&lt;code&gt;npm run-script build&lt;/code&gt;) them. Then link the &lt;em&gt;./build&lt;/em&gt; folder instead of the base level of the cloned repository. (Similarly for Jekyll sites, but use the &lt;em&gt;./_output&lt;/em&gt; folder instead.) As for active servers, just make sure your server is running on the same port as it is listed in the configuration file.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#set-up-https-with-certbot&#34;&gt;
    &lt;h2 id=&#34;set-up-https-with-certbot&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Set up HTTPS with certbot&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Thanks to Let’s Encrypt, you can now get free and easy HTTPS certificates. With the introduction of certbot, everything just got even easier!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Reference from &lt;a href=&#34;https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-16-04&#34;&gt;How To Secure Nginx with Let’s Encrypt on Ubuntu 16.04&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo add-apt-repository ppa:certbot/certbot
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo apt-get update
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo apt-get install python-certbot-nginx
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo certbot --nginx -d binhong.me -d www.binhong.me -d api.breakups.life -d breakups.life -d www.breakups.life
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Just run the above for all your domain and subdomain names and certbot will take care of everything. If you were to renew the certs, you can run the following so the certbot will help you renew your SSL certificate.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo certbot renew --dry-run
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;a class=&#34;anchor&#34; href=&#34;#updating-everything&#34;&gt;
    &lt;h2 id=&#34;updating-everything&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Updating everything&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Now that you have everything up and running, you might be thinking, well there seems to be an awful lot to remember if/when I need to update something. Unfortunately, that’s kinda true, but we can always make it easier by adding a script that does it for us.&lt;/p&gt;
&lt;p&gt;Here is how one would look:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;25
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;26
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;27
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;28
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;29
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;30
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;31
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;32
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;33
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;34
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;35
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Update SSL certificates&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo certbot renew
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;killall screen
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# normal html websites&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; websiteSource
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git pull
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; ../
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# node.js static site&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; nodeProjectFolder
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git pull
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;npm install
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;npm run-script build
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; ../
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# ruby jekyll site&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; jekyllFolder
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git pull
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;bundle install
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;bundle &lt;span class=&#34;nb&#34;&gt;exec&lt;/span&gt; jekyll build
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; ../
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# node.js server&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# make sure to stop the existing session before going ahead to start a new one&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; nodeServer
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git pull
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;screen -dmS nodeServer
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;npm install
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;screen -S nodeServer -X stuff &lt;span class=&#34;s2&#34;&gt;&amp;#34;cd ~/nodeServer
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;screen -S nodeServer -X stuff &lt;span class=&#34;s2&#34;&gt;&amp;#34;npm start
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; ../
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Thanks for reading! Let me know if you have any questions in the comments below.&lt;/p&gt;
&lt;hr&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#other-references&#34;&gt;
    &lt;h2 id=&#34;other-references&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Other References&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://serverfault.com/questions/363159/nginx-proxy-pass-redirects-ignore-port&#34;&gt;nginx proxy pass redirects ignore port&lt;/a&gt; on &lt;a href=&#34;https://serverfault.com/&#34;&gt;serverfault&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://superuser.com/questions/632205/continue-ssh-background-task-jobs-when-closing-ssh&#34;&gt;Continue SSH background task/jobs when closing SSH&lt;/a&gt; on &lt;a href=&#34;https://superuser.com/&#34;&gt;superuser&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href=&#34;https://medium.freecodecamp.org/how-you-can-host-multiple-domain-names-and-projects-in-one-vps-7aed4f56e7a1&#34;&gt;freeCodeCamp Medium&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
    <item>
      <title>Hosting your own Git server with Gitea</title>
      <link>https://binhong.me/blog/2018-08-20-hosting-your-own-git-server-with-gitea/</link>
      <pubDate>Mon, 20 Aug 2018 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2018-08-20-hosting-your-own-git-server-with-gitea/</guid>
      <description>&lt;p&gt;Most people (including myself) host their personal projects on a third-party free Git hosting websites like GitHub, GitLab, Bitbucket etc. While that is sufficient for most people, it is also pretty fun to have your own Git service hosted on your own domain name of choice. While I trust my fellow engineers at these companies, I am using this as a personally backup to those services if anything goes wrong (&lt;a href=&#34;https://about.gitlab.com/2017/02/01/gitlab-dot-com-database-incident/&#34;&gt;like GitLab&lt;/a&gt;. Though to their credit, they were able to &lt;a href=&#34;https://about.gitlab.com/2017/02/10/postmortem-of-database-outage-of-january-31/&#34;&gt;recover most of it&lt;/a&gt;).&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#requirements&#34;&gt;
    &lt;h2 id=&#34;requirements&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Requirements&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;There really isn&amp;rsquo;t much requirements except for a working machine and a proper working network. Preferably, you would be hosting this on a Virtual Private Server (DigitalOcean, Vultr, Linode etc.). However, you can also mess around with it on your own machine (it takes and requires very little resources by itself) or even host it at home on a Raspberry Pi!&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#installation&#34;&gt;
    &lt;h2 id=&#34;installation&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Installation&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;&lt;em&gt;Referenced from &lt;a href=&#34;https://docs.gitea.io/en-us/install-from-binary/&#34;&gt;Gitea&amp;rsquo;s own documentation&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;While there is a Gitea package in Debian&amp;rsquo;s contrib (which most popular Linux OS such as Ubuntu and Mint based itself on), it doesn&amp;rsquo;t seem it was maintained by the Gitea folks themselves so I wouldn&amp;rsquo;t advice using it. Instead, the instructions below will provide an easy way to just download and run it with binary.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#database&#34;&gt;
    &lt;h2 id=&#34;database&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Database&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;You&amp;rsquo;ll need a database to maintain all the information of user accounts and such. Gitea comes with support for SQLite, MySQL and PostgreSQL out of the box so you can pick either of those that you are familiar with to go with. Personally I picked PostgreSQL as I&amp;rsquo;ve always wanted to learn how to mess with one.
In your choice of database, you should create a user (preferably only specifically for the use of Gitea) and a database for Gitea. You will need to fill out these credentials later when setting up Gitea.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#gitea&#34;&gt;
    &lt;h2 id=&#34;gitea&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Gitea&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;As Gitea calls itself &amp;lsquo;a painless self-hosted Git service&amp;rsquo;, the setup is indeed very straightforward. Simply download and run it depending on they way you want to do it.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;wget -O gitea https://dl.gitea.io/gitea/1.5.0/gitea-1.5.0-linux-amd64
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;chmod +x gitea
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;em&gt;Optionally&lt;/em&gt;, you can also verify the GPG signature of the downloaded file with Gitea&amp;rsquo;s &lt;a href=&#34;https://pgp.mit.edu/pks/lookup?op=vindex&amp;amp;amp;fingerprint=on&amp;amp;amp;search=0x2D9AE806EC1592E2&#34;&gt;GPG key&lt;/a&gt; for security purposes before running it.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;gpg --keyserver pgp.mit.edu --recv 0x2D9AE806EC1592E2
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;gpg --verify gitea-1.5.0-linux-amd64.asc gitea-1.5.0-linux-amd64
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;To run Gitea, it is as simple as the following…&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;./gitea web
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;By default, it will be running on port::3000 of the device IP (http://localhost:3000/). In case you prefer running Gitea from source, they also have &lt;a href=&#34;https://docs.gitea.io/en-us/install-from-source/&#34;&gt;specific documentations&lt;/a&gt; for that. I had a lot of troubles trying to set it up myself so YMMV.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#load-balancer-optional&#34;&gt;
    &lt;h2 id=&#34;load-balancer-optional&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Load Balancer (Optional)&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;&lt;em&gt;Referenced from &lt;a href=&#34;https://docs.gitea.io/en-us/reverse-proxies/&#34;&gt;Gitea&amp;rsquo;s own documentation&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If you are running this on a remote server (especially if you are also hosting a bunch of other stuffs), you will need to use a load balance (like Apache HTTPD or NGINX) to forward a specific domain to the port. Append your configuration file with the following as you see fit.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#apache-httpd&#34;&gt;
    &lt;h4 id=&#34;apache-httpd&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Apache HTTPD&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h4&gt;
&lt;/a&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-apache&#34; data-lang=&#34;apache&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;&amp;lt;VirtualHost&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;*:80&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;ProxyPreserveHost&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;On&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;ProxyRequests&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;off&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;ProxyPass&lt;/span&gt; / http://localhost:3000/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;ProxyPassReverse&lt;/span&gt; / http://localhost:3000/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;&amp;lt;/VirtualHost&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;a class=&#34;anchor&#34; href=&#34;#nginx&#34;&gt;
    &lt;h4 id=&#34;nginx&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;NGINX&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h4&gt;
&lt;/a&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-nginx&#34; data-lang=&#34;nginx&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;server&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;listen&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;80&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;server_name&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;git.example.com&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;location&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;/&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kn&#34;&gt;proxy_pass&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;http://localhost:3000&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;a class=&#34;anchor&#34; href=&#34;#configuration&#34;&gt;
    &lt;h2 id=&#34;configuration&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Configuration&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Open your browser and navigate to the wherever Gitea was hosted. In there, you will be filling out the database settings as you have configured above (for PostgreSQL, it should be on &lt;code&gt;127.0.0.1:5432&lt;/code&gt;, not sure about the others).&lt;/p&gt;
&lt;p&gt;Few things to look out for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Domain / Application URL&lt;/code&gt; - This is important to be set up properly as they are used for SSH and HTTPS connection when it comes to cloning and pushing.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Disable Self-registration&lt;/code&gt; - Considering that you are probably setting this up only for your own use, you should select this option so random people wouldn&amp;rsquo;t just sign up and host projects on your server.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Admin username and password&lt;/code&gt; - Since you have disabled self registration, you should then set this up in here and use it as your primary account. If you are concerned about security, you can also use the admin account to setup a day-to-day use account so in case the day-to-day account is compromised, you still retain full control of the server through your admin account.&lt;/li&gt;
&lt;/ul&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#usage&#34;&gt;
    &lt;h2 id=&#34;usage&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Usage&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;For the most part, I&amp;rsquo;m using this as a backup to my projects hosted on GitHub and GitLab (project pages is an amazing feature) so ideally, I am mirroring most (if not all) of my projects. As part of being a mirrored repository, Gitea will periodically check the source repository for updates so you don&amp;rsquo;t have to manually sync them. That said, I have quite a few projects that I moved away from mirroring and I&amp;rsquo;ll talk about why below.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#maintenance&#34;&gt;
    &lt;h2 id=&#34;maintenance&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Maintenance&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;In case where your VPS (or Raspberry Pi) might restart constantly or so, it might be a chore to always manually start the service every time it is restarted. You should then consider configuring Gitea to run as a service (&lt;a href=&#34;https://docs.gitea.io/en-us/linux-service/&#34;&gt;Ubuntu&lt;/a&gt;, &lt;a href=&#34;https://docs.gitea.io/en-us/windows-service/&#34;&gt;Windows&lt;/a&gt;).&lt;/p&gt;
&lt;hr&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#droneio&#34;&gt;
    &lt;h2 id=&#34;droneio&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Drone.io&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;When talking about the topic of Git, its hard to stay away from CI. It is such a great idea of automation that saves so much pain and problem especially if it is a huge project that takes a lot of resources and hard to run the tests locally. Here, I&amp;rsquo;ll talk about &lt;a href=&#34;https://drone.io&#34;&gt;drone.io&lt;/a&gt; which can also be self-hosted along with your Gitea server.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#requirements-1&#34;&gt;
    &lt;h2 id=&#34;requirements-1&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Requirements&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;You will need to have &lt;a href=&#34;https://www.docker.com/&#34;&gt;docker&lt;/a&gt; installed in your machine as drone as a whole relies quite a bit on docker to work. The machine where drone is hosted will also require quite a bit of memory and processing power as running CI can be quite a chore for servers.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#installation-1&#34;&gt;
    &lt;h2 id=&#34;installation-1&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Installation&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;&lt;em&gt;Referenced from &lt;a href=&#34;http://docs.drone.io/installation/&#34;&gt;drone&amp;rsquo;s own documentation&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;First, get the docker image for drone.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;docker pull drone/drone:0.8
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Next, create a file named &lt;code&gt;docker-compose.yaml&lt;/code&gt; and fill in the following.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;25
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;26
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;27
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;28
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;29
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;30
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;31
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;32
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;33
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;version&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;2&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;services&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;drone-server&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;drone/drone:0.8&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;ports&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;m&#34;&gt;8000&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;8000&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;m&#34;&gt;9000&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;9000&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;volumes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;/var/lib/drone:/var/lib/drone/&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;restart&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;always&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;environment&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;DRONE_OPEN=true&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;DRONE_HOST=http://drone.example.com&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;DRONE_GITEA=true&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;DRONE_GITEA_URL=http://git.example.com&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;DRONE_SECRET=${DRONE_SECRET}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;drone-agent&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;drone/agent:0.8&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;command&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;agent&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;restart&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;always&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;depends_on&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;drone-server&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;volumes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;/var/run/docker.sock:/var/run/docker.sock&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;environment&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;DRONE_SERVER=drone-server:9000&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;DRONE_SECRET=${DRONE_SECRET}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;DRONE_DATABASE_DRIVER:postgres&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;DRONE_DATABASE_DATASOURCE:postgres://root:password@1.2.3.4:5432/postgres?sslmode=disable&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Few things to keep in mind in the file above:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DRONE_SECRET&lt;/code&gt; - This is a secret string of your choice used for authentication purpose.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DRONE_HOST&lt;/code&gt; - URL where this drone server is hosted.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DRONE_GITEA_URL&lt;/code&gt; - Link to your Gitea server location (it can also be &lt;code&gt;http://localhost:3000/&lt;/code&gt; link)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DRONE_DATABASE_DRIVER&lt;/code&gt; - Database setup is &lt;strong&gt;optional&lt;/strong&gt;. In their &lt;a href=&#34;http://docs.drone.io/database-settings/&#34;&gt;documentation&lt;/a&gt;, they mention the setup process of MySQL and PostgreSQL. Though do note that their documentation doesn&amp;rsquo;t seems to be accurate at least &lt;a href=&#34;https://github.com/drone/docs/pull/360&#34;&gt;from my experience&lt;/a&gt; setting it up.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the same directory run the following and drone will be running on &lt;code&gt;http://localhost:8000/&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;docker-compose up
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;a class=&#34;anchor&#34; href=&#34;#load-balancer-optional-1&#34;&gt;
    &lt;h2 id=&#34;load-balancer-optional-1&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Load Balancer (Optional)&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Similarly, if this is hosted on a remote server, you will have to add the following with the load balancer of your choice.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#apache-httpd-1&#34;&gt;
    &lt;h4 id=&#34;apache-httpd-1&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Apache HTTPD&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h4&gt;
&lt;/a&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-apache&#34; data-lang=&#34;apache&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;&amp;lt;VirtualHost&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;*:80&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;ProxyPreserveHost&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;On&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;RequestHeader&lt;/span&gt; set X-Forwarded-Proto &lt;span class=&#34;s2&#34;&gt;&amp;#34;https&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;ProxyPass&lt;/span&gt; / http://127.0.0.1:8000/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;ProxyPassReverse&lt;/span&gt; / http://127.0.0.1:8000/
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;&amp;lt;/VirtualHost&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;a class=&#34;anchor&#34; href=&#34;#nginx-1&#34;&gt;
    &lt;h4 id=&#34;nginx-1&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;NGINX&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h4&gt;
&lt;/a&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-nginx&#34; data-lang=&#34;nginx&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;server&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;listen&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;80&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;server_name&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;drone.example.com&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kn&#34;&gt;location&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;/&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kn&#34;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;X-Forwarded-For&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$remote_addr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kn&#34;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;X-Forwarded-Proto&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$scheme&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kn&#34;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;Host&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$http_host&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kn&#34;&gt;proxy_pass&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;http://127.0.0.1:8000&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kn&#34;&gt;proxy_redirect&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;off&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kn&#34;&gt;proxy_http_version&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;.1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kn&#34;&gt;proxy_buffering&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;off&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kn&#34;&gt;chunked_transfer_encoding&lt;/span&gt; &lt;span class=&#34;no&#34;&gt;off&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;a class=&#34;anchor&#34; href=&#34;#usage-1&#34;&gt;
    &lt;h2 id=&#34;usage-1&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Usage&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;As mentioned, drone relies on docker not only for its installation but also running the CI itself. Each repository that you want to configure for drone should have to have a &lt;code&gt;.drone.yml&lt;/code&gt; file like below at the top level of the repository. &lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;pipeline&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;build&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;image&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;node:${NODE_VERSION}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;commands&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;npm install&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;npm run test&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;matrix&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;NODE_VERSION&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;latest&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;s2&#34;&gt;&amp;#34;7&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;s2&#34;&gt;&amp;#34;6&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;s2&#34;&gt;&amp;#34;4&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Few things to pay attention to in the sample file:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;image&lt;/code&gt; - Drone supports any docker images available for download. You can look for available docker images at the Docker Hub.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;matrix&lt;/code&gt; - Like the scenario above, you want to test the project in multiple environments but doesn&amp;rsquo;t want to retype most of the stuffs, you can use matrix as a variable to hold all the different versions you would like to run tests on.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;commands&lt;/code&gt; - This is the list of commands to run for the test to take place. You should also include installation of project dependent packages (such as &lt;code&gt;mvn install&lt;/code&gt;, &lt;code&gt;npm install&lt;/code&gt;, &lt;code&gt;bundle install&lt;/code&gt; etc).&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#personal-notes&#34;&gt;
    &lt;h2 id=&#34;personal-notes&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Personal Notes&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#mirrored-repositories&#34;&gt;
    &lt;h4 id=&#34;mirrored-repositories&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Mirrored Repositories&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h4&gt;
&lt;/a&gt;
&lt;p&gt;Currently from my own experimentation, adding new commits to mirrored repositories (which are read-only) that are mainly hosted elsewhere would not trigger drone to run CI. Therefore, if you were to run drone on your own server, you can either enable it directly with the origin of the repository mirror or push twice to 2 separate servers for every commit. I&amp;rsquo;m still looking for a solution for this so hopefully there&amp;rsquo;s a better way than that.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#memory-issues&#34;&gt;
    &lt;h4 id=&#34;memory-issues&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Memory issues&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h4&gt;
&lt;/a&gt;
&lt;p&gt;Some of these instances can take quite a bit of memory spaces to run. Personally, I am hosting this on a 256MB VPS squeezing it with a bunch of other stuffs I hosts. This actually causes an issue where the tests cannot be run properly. As seen &lt;a href=&#34;https://drone.binhong.me/binhong/LibrarySystem/3/3&#34;&gt;here&lt;/a&gt;, at the end of the &lt;code&gt;build&lt;/code&gt; log, it was just abruptly &amp;lsquo;Killed&amp;rsquo; and ended there and then with no further information. I am still working on investigating this but it is very likely due to the lack of remaining unused memory space on the VPS.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#static-ip&#34;&gt;
    &lt;h4 id=&#34;static-ip&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Static IP&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h4&gt;
&lt;/a&gt;
&lt;p&gt;In case you are hosting this on a Raspberry Pi at home for personal use, you would want to set a static IP direction on your router. This would help make sure that when in your network, you can always connect to the device through the same IP.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#nginx-2&#34;&gt;
    &lt;h4 id=&#34;nginx-2&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;NGINX&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h4&gt;
&lt;/a&gt;
&lt;p&gt;&lt;del&gt;I did not go deep into either of the load balancers in here as I am currently working on a separate piece that will focus more onto the configuration and use of NGINX to hosting multiple domain names and &amp;lsquo;servers&amp;rsquo;. Keep a look out for that!&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;You can read more about NGINX on my next post &lt;a href=&#34;http://localhost:1313/blog/2018-08-29-how-to-host-multiple-domain-names-and-projects-on-one-server/&#34;&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#references&#34;&gt;
    &lt;h2 id=&#34;references&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;References&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://bryangilbert.com/post/devops/how-to-setup-gitea-ubuntu/&#34;&gt;How to Setup Gitea on an Ubuntu Server&lt;/a&gt; by &lt;a href=&#34;http://bryangilbert.com/&#34;&gt;Bryan Gilbert&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.slideshare.net/appleboy/introduction-to-gitea-with-drone&#34;&gt;Introduction to Gitea with Drone&lt;/a&gt; by &lt;a href=&#34;https://www.slideshare.net/appleboy&#34;&gt;Bo-Yi Wu&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href=&#34;https://codeburst.io/hosting-your-own-git-server-with-gitea-fc3298aa15ce&#34;&gt;codeburst.io&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
    <item>
      <title>Building a side project over the weekend</title>
      <link>https://binhong.me/blog/2018-06-02-building-a-side-project-over-the-weekend/</link>
      <pubDate>Sat, 02 Jun 2018 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2018-06-02-building-a-side-project-over-the-weekend/</guid>
      <description>&lt;p&gt;Few months ago during my job search, I decided that I want to add some new skills to my arsenal (resume) so I went ahead to build a &lt;a href=&#34;http://www.breakups.life/&#34;&gt;bill splitting webapp&lt;/a&gt; (and the month before, &lt;a href=&#34;https://github.com/binhonglee/dota2-random&#34;&gt;an Alexa skill&lt;/a&gt;). Granted, the backend was built weeks before during a hackathon (shoutout to &lt;a href=&#34;http://www.hackisu.org/&#34;&gt;HackISU&lt;/a&gt;), I’m just focusing on building the UI that makes post requests to the backend. In the process, I also ended up fixing some inherent flaws I left behind in the backend.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#idea&#34;&gt;
    &lt;h2 id=&#34;idea&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Idea&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Be realistic. You don’t want to end your weekend with an unfinished product that would cause you to lose focus of work / study during the week because you &lt;em&gt;“really need to go back and get it done with”&lt;/em&gt;. On the other hand, you also don’t want to just straight away give up in your first few hours because &lt;em&gt;“it’s impossible”&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;In cases where the thing you really wanted to build is huge and complicated, try breaking it down into smaller pieces and maybe span it over multiple weekends. The point being to have a realistic idea of what to expect at the end of the weekend and achieve it while not mounting yourself additional pressure (and distractions) to &lt;em&gt;‘complete’&lt;/em&gt; the other parts of it during your work / study week that would jeopardize what you are supposed to be doing during the day.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://binhong.me/blog/img/sideProject2.jpg&#34; target=&#34;_blank&#34;&gt;
    
    &lt;img src=&#34;https://binhong.me/blog/2018-06-02-building-a-side-project-over-the-weekend//blog/img/sideProject2.jpg&#34;&gt;
    
&lt;/a&gt;
&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#preparation&#34;&gt;
    &lt;h2 id=&#34;preparation&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Preparation&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#plan&#34;&gt;
    &lt;h4 id=&#34;plan&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Plan&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h4&gt;
&lt;/a&gt;
&lt;p&gt;I would be lying if I said I have a detailed plan of how long I’ll take to get each part done. I just draft up the most minimal version that I think covers the basic need and leave it at that. Don’t over dictate yourself that you don’t have flexibility in case something happened to be harder than you thought, which is almost always the case.&lt;/p&gt;
&lt;p&gt;Pick something you are not already familiar with. The best way to improve is to get out of your comfort zone. It’s also only a weekend long so even if that turn out to be something you dislike, at least you now have a solid opinion backed with experience on that.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#food--beverages&#34;&gt;
    &lt;h4 id=&#34;food--beverages&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Food &amp;amp; beverages&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h4&gt;
&lt;/a&gt;
&lt;p&gt;I think this is a major one. Figure out how you’re going to get food supplies over the weekend that will reduce the amount of time you spend not coding. If you usually cook for yourself, just make sure to look for meals that takes little time to prepare and prepare ahead of time too.&lt;/p&gt;
&lt;p&gt;Also do you even Soylent? (Credit to &lt;a href=&#34;https://twitter.com/wongmjane&#34;&gt;Jane Manchun Wong&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://binhong.me/blog/img/sideProject3.jpg&#34; target=&#34;_blank&#34;&gt;
    
    &lt;img src=&#34;https://binhong.me/blog/2018-06-02-building-a-side-project-over-the-weekend//blog/img/sideProject3.jpg&#34;&gt;
    
&lt;/a&gt;
&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#process&#34;&gt;
    &lt;h2 id=&#34;process&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Process&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Hold yourself accountable. I’m not saying to reject every single invitation from your friends to hang out but be you have to be aware and be flexible of your schedule. If you are in the train of thoughts or &lt;em&gt;‘in the zone’&lt;/em&gt; to finish up one major part of it, let your friends or whoever you’re gonna hang with know slightly in advance. I’m sure they wouldn’t mind the slightly additional wait while you have a peace of mind out partying or doing whatever instead of subconsciously constantly thinking about what you would be implementing.&lt;/p&gt;
&lt;p&gt;Take it slow. The faster you go in the beginning, the faster you burn out. This happens a lot and everywhere. It can be overwhelming in the beginning so you slowly take your time to walk through things and watch some tutorials (YouTube is amazing) to get help on learning to build whatever you wanted to (especially with new technologies you never tried).&lt;/p&gt;
&lt;p&gt;Take frequent breaks. I can’t stress how important it is to relax in between. You don’t get maximum efficiency by rushing things, you get them by being in between too comfortable and too overwhelmed. Personally, I would take a break and go out for a walk and fresh air every 30 mins or so. This highly differs among individuals so you have to figure out what works best for you.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://binhong.me/blog/img/sideProject4.jpg&#34; target=&#34;_blank&#34;&gt;
    
    &lt;img src=&#34;https://binhong.me/blog/2018-06-02-building-a-side-project-over-the-weekend//blog/img/sideProject4.jpg&#34;&gt;
    
&lt;/a&gt;
&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#wrap-up&#34;&gt;
    &lt;h2 id=&#34;wrap-up&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Wrap up&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Don’t get too hung up on the project. I’m not saying you shouldn’t be proud of it but the skills you obtained in the process will very likely to be much more valuable than that. As mentioned, you don’t want to feel obligated to come back and ‘complete’ the project in the future when you’re procrastinating while you actually have other priorities like project deadlines, homeworks etc. so its important to wrap up your project properly in a way that its a minimal product presentable but also something you understand that you can always add onto whenever you feel like it.&lt;/p&gt;
&lt;p&gt;Of course, in an off chance that it blows up, congrats! For starters, maybe you can start thinking about how to monetize it? I don’t know (this has not happened to me). Maybe some guys at &lt;a href=&#34;https://www.facebook.com/groups/1401833413216649&#34;&gt;I can handle the business side&lt;/a&gt; can help you with that. 😉😂 /j&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href=&#34;https://blog.binhong.me/building-a-side-project-over-the-weekend-8895e0cf02d7&#34;&gt;my personal Medium publication&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
    <item>
      <title>Getting started with Travis-CI</title>
      <link>https://binhong.me/blog/2018-04-22-getting-started-with-travis-ci/</link>
      <pubDate>Sun, 22 Apr 2018 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2018-04-22-getting-started-with-travis-ci/</guid>
      <description>&lt;a class=&#34;anchor&#34; href=&#34;#what-is-ci-and-why-should-i-care-aboutit&#34;&gt;
    &lt;h2 id=&#34;what-is-ci-and-why-should-i-care-aboutit&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;What is CI and why should I care about it?&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;CI stands for Continuous Integration which as the name suggests, is an integrated tests that runs continuously on every time when someone pushes a commit (or mostly when opening a &lt;a href=&#34;https://help.github.com/articles/about-pull-requests/&#34;&gt;Pull Request&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;As for why is it important, it automates the whole testing modules. Ideally, you would still want to test your changes locally before pushing them to remote but in some occasion where you forgot or the program requires a lot more resources than your current machine can handle, CI comes in very handy. That aside, if your project happen to have people wanting to contribute code, a properly written test with CI will help you in reviewing the changes and ensuring that the contribution would not break the build.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#why-travis-ci&#34;&gt;
    &lt;h2 id=&#34;why-travis-ci&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Why Travis-CI?&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;There are plenty of CI tools out there (cricleCI, AppVeyor etc.) so why Travis-CI? The straightforward answer is that its the easiest one to get started with especially if you are already on GitHub. This is actually also one of its major drawback which is how tightly tied Travis-CI is onto GitHub. As far as I know, it is not possible to use Travis-CI outside of GitHub. That aside, if you host your projects on GitHub, that should be the least of your concern.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#setup&#34;&gt;
    &lt;h2 id=&#34;setup&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Setup&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;First, sign up at &lt;a href=&#34;https://travis-ci.org/&#34;&gt;their website&lt;/a&gt;, link your GitHub account and enable it for the repository you would like to implement this. Do note that this is only for public repositories. If you would like to do this for a private repository instead, you will need to sign up for &lt;a href=&#34;https://docs.travis-ci.com/user/travis-ci-for-private/&#34;&gt;Travis-CI Pro&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Create a file named &lt;code&gt;.travis.yml&lt;/code&gt; and add the first line with the programming language you used (unless its Android in which you would then write Android instead of Java).&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;language&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;java&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c&#34;&gt;# (or cpp, android, node_js etc.)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Now, there’s also a possibility that running the test might require sudo access to the testing environment in which you would then add the following line above the &lt;code&gt;language&lt;/code&gt; line.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;sudo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;required&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;language&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;cpp&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;a class=&#34;anchor&#34; href=&#34;#testing-environment&#34;&gt;
    &lt;h2 id=&#34;testing-environment&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Testing environment&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Ideally, you will only be testing it in one environment which means this will be a one liner of this.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;os&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;linux&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;However, in most occasion, we will be testing our software with a few different kinds of environment (at the time of writing, Travis-CI seems to only Ubuntu and OSX as seen &lt;a href=&#34;https://docs.travis-ci.com/user/reference/overview/&#34;&gt;here&lt;/a&gt;). Sometimes, 2 lines is enough.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;os&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;linux&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;osx&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;But more time than not, you need complicated configuration for each one of them (especially when it comes to C++ projects).&lt;/p&gt;
&lt;p&gt;Here is how the environment configuration for &lt;a href=&#34;https://github.com/binhonglee/TicketingSystem/blob/master/.travis.yml&#34;&gt;one of my projects&lt;/a&gt; looks like (shortened):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;matrix&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;include&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;os&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;linux&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;addons&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;apt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;sources&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;ubuntu-toolchain-r-test&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;          &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;packages&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;g++-7&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;env&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;MATRIX_EVAL=&amp;#34;CC=gcc-7 &amp;amp;&amp;amp; CXX=g++-7&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;os&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;osx&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;osx_image&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;xcode8&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;env&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;MATRIX_EVAL=&amp;#34;brew update &amp;amp;&amp;amp; brew install gcc &amp;amp;&amp;amp; CC=gcc-7 &amp;amp;&amp;amp; CXX=g++-7&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;a class=&#34;anchor&#34; href=&#34;#language-specificsettings&#34;&gt;
    &lt;h2 id=&#34;language-specificsettings&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Language specific settings&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;For some languages, it is a lot more complicated than the others due to the way their versioning works.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#android&#34;&gt;
    &lt;h3 id=&#34;android&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Android&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h3&gt;
&lt;/a&gt;
&lt;p&gt;This is probably the most complicated one I’ve come across. It took a lot of tries and failure for me to figure out how it works. (&lt;a href=&#34;https://docs.travis-ci.com/user/languages/android/&#34;&gt;The documentation&lt;/a&gt; has also since updated which helps greatly.)&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;android&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;components&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;tools&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;platform-tools&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;build-tools-26.0.2&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;android-26&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;platform-tools&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;extra-android-support&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;extra-google-google_play_services&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;extra-google-m2repository&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;extra-android-m2repository&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;addon-google_apis-google-26&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;sys-img-armeabi-v7a-android-26&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;a class=&#34;anchor&#34; href=&#34;#c&#34;&gt;
    &lt;h3 id=&#34;c&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;C++&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h3&gt;
&lt;/a&gt;
&lt;p&gt;There isn’t specific custom configuration available for C++ like for Android above but due to the nature of its complicated OS configuration, you will need to import the matrix from before into the system before installing anything that might be needed to run the test itself. Therefore you will need to add the following.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;before_install&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;eval &amp;#34;${MATRIX_EVAL}&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;a class=&#34;anchor&#34; href=&#34;#java&#34;&gt;
    &lt;h3 id=&#34;java&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Java&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h3&gt;
&lt;/a&gt;
&lt;p&gt;For Java, due to the differences between OpenJDK, OracleJDK and also differences from versions to versions itself (shoutout to OracleJDK 9 &amp;amp; 10), you might want to test your software with every version available.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;jdk&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;oraclejdk9&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;oraclejdk8&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;openjdk8&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;openjdk7&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;a class=&#34;anchor&#34; href=&#34;#python&#34;&gt;
    &lt;h3 id=&#34;python&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Python&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h3&gt;
&lt;/a&gt;
&lt;p&gt;Similarly, you can test your software or package against different versions of Python including the development versions.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;python&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;s2&#34;&gt;&amp;#34;2.7&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;s2&#34;&gt;&amp;#34;3.6&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;s2&#34;&gt;&amp;#34;3.6-dev&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;s2&#34;&gt;&amp;#34;pypy2.7&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;s2&#34;&gt;&amp;#34;pypy3.8&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;em&gt;*Note: Due to recent changes, seems like &lt;code&gt;3.7-dev&lt;/code&gt;, &lt;code&gt;3.8-dev&lt;/code&gt;, and &lt;code&gt;nightly&lt;/code&gt; would not work as of now.&lt;/em&gt;&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#nodejs&#34;&gt;
    &lt;h3 id=&#34;nodejs&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Node.js&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h3&gt;
&lt;/a&gt;
&lt;p&gt;Again, you might need to test your software with multiple versions or node.js except the differences between each version is not as drastic but there are a lot more versions comparatively.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;node_js&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;s2&#34;&gt;&amp;#34;node&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;s2&#34;&gt;&amp;#34;9&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;s2&#34;&gt;&amp;#34;8&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;s2&#34;&gt;&amp;#34;7&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;s2&#34;&gt;&amp;#34;6&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;s2&#34;&gt;&amp;#34;5&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;s2&#34;&gt;&amp;#34;4&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;s2&#34;&gt;&amp;#34;lts/*&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;By doing this, you will be testing your software towards possibly 8 versions of node.js.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#install-and-runtest&#34;&gt;
    &lt;h2 id=&#34;install-and-runtest&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Install and run test&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;If you look into the &lt;a href=&#34;https://docs.travis-ci.com/user/languages/&#34;&gt;specific documentations&lt;/a&gt; about the configurations for each languages, you will find that many of them has default &lt;code&gt;install&lt;/code&gt; and &lt;code&gt;test&lt;/code&gt; scripts but you can always overwrite them with your own scripts or commands like the following.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;before_install&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;./beforeInstall.sh&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;install&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;./install.sh&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;before_script&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;./preTest.sh&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;script&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;./test.sh&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;em&gt;*Note:  if you testing in macOS environment and is installing any dependencies through Homebrew (including language dependencies in C++), you will have to do a &lt;code&gt;brew update&lt;/code&gt; before running anything brew related to prevent an error.&lt;/em&gt;&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#deployment&#34;&gt;
    &lt;h2 id=&#34;deployment&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Deployment&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;If you host your project externally while using your GitHub repository as your code control, Travis-CI can also help automating the deployment process. Depending on the service provider you use, their configuration can vary greatly from one another. Below, I included some of those that I think is the more popular ones. As you might have guessed, you can also deploy to multiple developers at once. I recommend checking out the &lt;a href=&#34;https://docs.travis-ci.com/user/deployment&#34;&gt;documentation page&lt;/a&gt; before implementing any of these.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;25
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;26
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;27
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;28
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;29
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;30
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;31
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;before_deploy&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;./before_deployment&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;deploy&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;provider&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;heroku&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;api_key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;YOUR_API_KEY&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;skip_cleanup&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;on&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;production&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;s2&#34;&gt;&amp;#34;./deploy&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;provider&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;pages&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;github_token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;$GITHUB_TOKEN&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;skip_cleanup&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;on&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;production&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;run&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;- &lt;span class=&#34;s2&#34;&gt;&amp;#34;./deploy&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;provider&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;npm&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;api_key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;YOUR_API_KEY&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;email&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;email@domain.com&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;on&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;production&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;provider&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;elasticbeanstalk&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;access_key_id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;&amp;lt;access-key-id&amp;gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;secret_access_key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;secure&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;Encypted &amp;lt;secret-access-key&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;region&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;us-east-1&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;app&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;your-app-name&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;env&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;your-app-environment&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;nt&#34;&gt;provider&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;pypi&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;user&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;username&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;password&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;password&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;after_deploy&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;./post_deployment&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;hr&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#code-coverage&#34;&gt;
    &lt;h2 id=&#34;code-coverage&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Code coverage&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Along with testing, I would also recommend using a tool to analyze your code coverage. It will show you how much coverage your test has over your codes (and usually, use cases). Personally, I use the tool called &lt;a href=&#34;https://codecov.io/&#34;&gt;codecov&lt;/a&gt;.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#add-thebadge&#34;&gt;
    &lt;h2 id=&#34;add-thebadge&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Add the badge!&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;If you are like me, the reason I was hooked up by this is because long time ago when I first got on GitHub, I saw plenty of repositories have the badge that shows if their build is &lt;code&gt;passing&lt;/code&gt; or &lt;code&gt;failing&lt;/code&gt;. I thought that was really fancy and wanted one of those so I started getting myself to learn what CI as a whole is about and how can I make use of it. Turns out, its a lot more useful than just being pretty!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href=&#34;https://codeburst.io/getting-started-with-travis-ci-f3223082f256&#34;&gt;codeburst.io&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
    <item>
      <title>RESTful API with Spark Kotlin</title>
      <link>https://binhong.me/blog/2018-03-21-restful-api-with-spark-kotlin/</link>
      <pubDate>Wed, 21 Mar 2018 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2018-03-21-restful-api-with-spark-kotlin/</guid>
      <description>&lt;p&gt;When speaking of building a backend API, the most common tools is either Spring Boot for Java or ExpressJS for JavaScript. Even for Kotlin, Spring Boot and JetBrain’s own ktor is the usual option to go with. But today I’ll explore into the less popular option of spark-kotlin.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Disclaimer : I am not in anyway affiliated nor do I work at the project. I just simply stumbled upon it and started using it when working on a side project. (coming soon™)&lt;/em&gt;&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#understanding-rest&#34;&gt;
    &lt;h2 id=&#34;understanding-rest&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Understanding REST&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;If you already know or built a RESTful API, you can skip this part. There are already quite a few &lt;a href=&#34;http://www.looah.com/source/view/2284&#34;&gt;guides&lt;/a&gt; out there that goes into the specifics of what REST is and all the amazing things about it. So instead, I’ll just cover the basics (more like, that’s all I know about it). It’s the standard way of communication on the internet through HTTP. In the world of internet, the frontend web interface needs a way to communicate with the backend. While the frontend can say all kinds of things, they still need to declare a set of standard ‘phrases’ between them so the backend knows what the frontend is asking for.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#setting-up&#34;&gt;
    &lt;h2 id=&#34;setting-up&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Setting up&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Here are a few thing you might want to have installed before getting started:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Maven / Gradle&lt;/li&gt;
&lt;li&gt;Kotlin&lt;/li&gt;
&lt;li&gt;Java? (not too sure but you’ll probably need it)&lt;/li&gt;
&lt;li&gt;(optional) IntelliJ&lt;/li&gt;
&lt;/ul&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#getting-started&#34;&gt;
    &lt;h2 id=&#34;getting-started&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Getting started&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;According to the &lt;a href=&#34;https://github.com/perwendel/spark-kotlin&#34;&gt;official GitHub repo of spark-kotlin&lt;/a&gt;, here are the following things to include to use it for either Maven or Gradle. Personally, I’m using Gradle for this simply because I’ve never used it outside of Android Development. (Also, its the new sexy thing. Kinda?)&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.sparkjava&lt;span class=&#34;nt&#34;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spark-kotlin&lt;span class=&#34;nt&#34;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.0.0-alpha&lt;span class=&#34;nt&#34;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-groovy&#34; data-lang=&#34;groovy&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;dependencies&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;compile&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;com.sparkjava:spark-kotlin:1.0.0-alpha&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;a class=&#34;anchor&#34; href=&#34;#the-basics&#34;&gt;
    &lt;h2 id=&#34;the-basics&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;The basics&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;This is what attracted me to use it over spring-boot. It just looks so much easier and straightforward comparatively. Admittedly, I’ve never used spring-boot for server development so I could be totally wrong especially if/when it comes down to a large scale application.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#get-request&#34;&gt;
    &lt;h3 id=&#34;get-request&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;GET request&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h3&gt;
&lt;/a&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-kotlin&#34; data-lang=&#34;kotlin&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;spark.kotlin.*&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;fun&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;main&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Array&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;/&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s2&#34;&gt;&amp;#34;Welcome to website abc.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;That’s it! I could’ve just ended the article here and you can pretty much figure out the rest on your own. But yea, let’s explore into a few more interesting features.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#post-request&#34;&gt;
    &lt;h3 id=&#34;post-request&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;POST request&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h3&gt;
&lt;/a&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-kotlin&#34; data-lang=&#34;kotlin&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;spark.kotlin.*&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;fun&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;main&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Array&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;post&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;/checkExist&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;checkItem&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;body&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;fun&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;checkItem&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;input&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;String&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;exists&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Exists.&amp;#34;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Does not exist.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;fun&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;exist&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;input&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Boolean&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;//Some check to make sure given string exist
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;For POST request, they work as easily too! All you need to know is that the request body can be retrieved with &lt;code&gt;request.body()&lt;/code&gt;.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#more-fun-stuff&#34;&gt;
    &lt;h2 id=&#34;more-fun-stuff&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;More fun stuff&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#redirect-params&#34;&gt;
    &lt;h3 id=&#34;redirect-params&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;redirect(), params()&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h3&gt;
&lt;/a&gt;
&lt;p&gt;Now, what if you want to have the same output for separate links? Well, you can just do &lt;code&gt;redirect(&amp;quot;/path&amp;quot;)&lt;/code&gt;. You can also get information from the URL through &lt;code&gt;params(&amp;quot;:path&amp;quot;)&lt;/code&gt; tag. Something like this…&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-kotlin&#34; data-lang=&#34;kotlin&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;spark.kotlin.*&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;fun&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;main&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Array&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;post&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;/checkExist&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;redirect&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;/checkExist/&amp;#34;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;request&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;body&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;/checkExist/:id&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;checkItem&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;params&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;:id&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;fun&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;checkItem&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;input&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;String&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;exist&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;input&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Exist.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;else&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Does not exist.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;fun&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;exist&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;input&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Boolean&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;//Some check to make sure given string exist
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;a class=&#34;anchor&#34; href=&#34;#instance-api&#34;&gt;
    &lt;h3 id=&#34;instance-api&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Instance API&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h3&gt;
&lt;/a&gt;
&lt;p&gt;You can also have a separate instance API that is running on a different port or with a different setting than the static API simply by declaring it as a separate variable.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-kotlin&#34; data-lang=&#34;kotlin&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;spark.kotlin.*&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;fun&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;main&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Array&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;val&lt;/span&gt; &lt;span class=&#34;py&#34;&gt;http&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ignite&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;port&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;4567&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;ipAddress&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;0.0.0.0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;http&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;/&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s2&#34;&gt;&amp;#34;Welcome to website abc.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;http&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;/404&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;status&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;404&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s2&#34;&gt;&amp;#34;Page does not exist.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Of course, there are a lot of more advanced features (such as session, uri, etc.) that I did not cover. Feel free to explore them on your own following the &lt;a href=&#34;https://github.com/perwendel/spark-kotlin/blob/master/README.md&#34;&gt;README of the repo&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;From here on out, I’m including some relevant but not exactly spark-kotlin stuff on it.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#dealing-with-json&#34;&gt;
    &lt;h2 id=&#34;dealing-with-json&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Dealing with JSON&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;In many (or most) occasions, both requests and responses will be in JSON format. Here’s where Google&amp;rsquo;s &lt;a href=&#34;https://github.com/google/gson&#34;&gt;Gson library&lt;/a&gt; comes in handy. It is really easy to use and had a wide range of support. I put together a simple example below to show how it is and can be used.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-kotlin&#34; data-lang=&#34;kotlin&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;com.google.gson.GsonBuilder&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;fun&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;main&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;args&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;Array&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// Some codes
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;val&lt;/span&gt; &lt;span class=&#34;py&#34;&gt;json&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;String&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;{&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;\&amp;#34;&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;\&amp;#34;&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;: 32151986,&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;\&amp;#34;&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;info&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;\&amp;#34;&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;: &lt;/span&gt;&lt;span class=&#34;se&#34;&gt;\&amp;#34;&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;someWords&lt;/span&gt;&lt;span class=&#34;se&#34;&gt;\&amp;#34;&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// Creating the item from JSON
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;val&lt;/span&gt; &lt;span class=&#34;py&#34;&gt;newItem&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Item&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;GsonBuilder&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;().&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;create&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;().&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;fromJson&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;json&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Item&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;::&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;class&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;java&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// Creating JSON string from item
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;print&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;GsonBuilder&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;().&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;setPrettyPrinting&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;().&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;create&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;().&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;toJson&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;newItem&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;data&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;Item&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;val&lt;/span&gt; &lt;span class=&#34;py&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;Long&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;val&lt;/span&gt; &lt;span class=&#34;py&#34;&gt;info&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;a class=&#34;anchor&#34; href=&#34;#unit-testing&#34;&gt;
    &lt;h2 id=&#34;unit-testing&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Unit testing&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;It took me a while to find a testing library that works as easily as I wished. This is a test library made specifically for the spark framework by &lt;a href=&#34;https://www.despegar.com/&#34;&gt;Despegar&lt;/a&gt; called &lt;a href=&#34;https://github.com/despegar/spark-test&#34;&gt;spark-test&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;25
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;26
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;27
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;28
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;29
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;30
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;31
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-kotlin&#34; data-lang=&#34;kotlin&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;com.despegar.http.client.*&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;com.despegar.sparkjava.test.SparkServer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;spark.servlet.SparkApplication&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;YourTest&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;SparkApplication&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;override&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;fun&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;init&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;main&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;arrayOf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;py&#34;&gt;testServer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;SparkServer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;YourTest&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;SparkServer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;YourTest&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;::&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;class&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;java&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;4567&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;nc&#34;&gt;Tests&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nd&#34;&gt;@Test&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;fun&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;getTest&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;py&#34;&gt;get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;GetMethod&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;testServer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;/&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;var&lt;/span&gt; &lt;span class=&#34;py&#34;&gt;httpResponse&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;testServer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;execute&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;assertEquals&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;httpResponse&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;code&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(),&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;200&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;assertEquals&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;httpResponse&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;body&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()),&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Welcome to website abc.&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;get&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;testServer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;/404&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;httpResponse&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;testServer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;execute&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;assertEquals&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;httpResponse&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;code&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(),&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;404&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nd&#34;&gt;@Test&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;fun&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;checkExistTest&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;val&lt;/span&gt; &lt;span class=&#34;py&#34;&gt;post&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;PostMethod&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;testServer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;post&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;/checkExist&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;someInput&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;val&lt;/span&gt; &lt;span class=&#34;py&#34;&gt;httpResponse&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;testServer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;execute&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;post&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;assertEquals&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;httpResponse&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;code&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(),&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;200&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;assertEquals&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;httpResponse&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;body&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()),&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Exist.&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// or &amp;#34;Does not exist.&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;A few things to keep in mind though:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;This is a Java library made for &lt;a href=&#34;https://github.com/perwendel/spark&#34;&gt;spark-java&lt;/a&gt; instead of &lt;a href=&#34;https://github.com/perwendel/spark-kotlin&#34;&gt;spark-kotlin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The server needs to be running on the background (in my case, on port 4567) for this to work&lt;/li&gt;
&lt;li&gt;Unable to attach a request body for DELETE request&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Note: As mentioned at the time of writing, the DELETE request is unable to attach any kind of request body to it making it hard to test your DELETE request. Personally, I use different URL each feature so I just redirect a POST request from the same url to the DELETE request and test it through the POST request instead. Not ideal, but it works for now.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#references&#34;&gt;
    &lt;h2 id=&#34;references&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;References&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@codemwnci/kotlin-webapp-tutorial-todolist-part-1-c544b9a70f29&#34;&gt;Kotlin WebApp Tutorial&lt;/a&gt; by Wayne Ellis&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@v.souhrada/build-rest-service-with-kotlin-spark-java-and-requery-part-1-1798844fdf04&#34;&gt;Build REST Service with Kotlin, Spark Java and Requery&lt;/a&gt; by Vaclav Souhrada&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href=&#34;https://hackernoon.com/restful-api-with-spark-kotlin-f43bd57affc4&#34;&gt;Hacker Noon&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
    <item>
      <title>Being lucky</title>
      <link>https://binhong.me/blog/2018-02-10-being-lucky/</link>
      <pubDate>Sat, 10 Feb 2018 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2018-02-10-being-lucky/</guid>
      <description>&lt;p&gt;Ah, you’ve graduated. Now what? Oh right, still no job offer! &lt;em&gt;*panics*&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;As an international student, I have come across hundreds of guides on “how to land an interview”, “how to get a job”, “how to solve DSA questions” etc but almost none of them were specifically written with an international student in mind. I’m not here to dismiss the quality of such guides but rather, to be an add-on (or plugin if you will), to help international students understand their challenges better.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#youve-got-a-lot-more-to-prove&#34;&gt;
    &lt;h2 id=&#34;youve-got-a-lot-more-to-prove&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;You’ve got a lot more to prove&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Let’s face it, with all the hassle for potential visa sponsorship or even losing you due to the visa lottery program, you have got a lot more to prove of being worthy of that extra investment. If they can hire someone to do the same job without all that hassle for similar or even slightly more expensive prices, they would hop on that right away. It just doesn’t make sense financially to invest in you when you have a higher chance of deciding not to or not being able to continue working for them 3 years later (after training you from a raw talent to a solid engineer).&lt;/p&gt;
&lt;p&gt;I have people around me who constantly complain about this. I do too sometimes but then I thought, I’m not ‘top of the class’ smart or ‘winner of multiple hackathons’ smart. Also, complaining about it won’t help your case. A job won’t fall from the sky randomly just because you complained hard enough. If you are just an average graduate (like myself), there is really very little incentive to think you are irreplaceable (technologically speaking) though that doesn’t mean you won’t be in 3 years time.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://binhong.me/blog/img/bl1.jpeg&#34; target=&#34;_blank&#34;&gt;
    
    &lt;img src=&#34;https://binhong.me/blog/2018-02-10-being-lucky//blog/img/bl1.jpeg&#34; alt=&#34;“Two books on a desk near a MacBook with lines of code on its screen” by Émile Perron on Unsplash&#34;&gt;
    
&lt;/a&gt;
&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#going-for-the-big-four&#34;&gt;
    &lt;h2 id=&#34;going-for-the-big-four&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Going for the big four&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;The big four in tech usually refers to Google, Facebook, Amazon and Apple (or Microsoft). Of course, anything in this paragraph also applies to most of the larger tech firms. These companies usually don’t take ‘the need of visa’ too much into consideration (if at all) as part of their hiring process but they also have higher barriers of entry than the smaller or more traditional companies.&lt;/p&gt;
&lt;p&gt;This means that you don’t need to worry so much about your application getting thrown out immediately after you check the ‘will need visa sponsorship in the future’ box. That said, you will need to be better than an average graduate. Spend some time on HackerRank, build some side projects, diversify your skill sets and most importantly, building connections. In the ocean of resumes that companies like these receive every year, it is really hard to stand out. One of the easiest way to get to the front of the line is through referrals.&lt;/p&gt;
&lt;p&gt;Go on LinkedIn, GitHub, local hackathons or your local developer meetups to chat and get connected with some engineers or even product managers. In many occasions, they would be more than happy to provide you with a referral since it would also net them some sweet bonuses. Before asking for referrals, make sure to do your own research through the company job site and shortlist about 3 positions you are interested in. Depending on the company and its referral system, this will probably make the job a lot easier for the person referring you.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#my-3-months-of-opt-unemployment-is-almost-up&#34;&gt;
    &lt;h2 id=&#34;my-3-months-of-opt-unemployment-is-almost-up&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;My 3 months of OPT unemployment is almost up!&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;The most popular way of extending this is to look for a professor you enjoy working with to help with his/her research. Alternatively, you can also look for a local super early stage registered startup or open source organization that might interest you and volunteer to provide free (or paid with equity) work.&lt;/p&gt;
&lt;p&gt;While the general consensus is to never work for free, you should be very clear with what you are getting back in return. For example, you might be interested in helping the cause of what the organization is working towards or that you really want to work with the CTO and learn from him directly. Things like this might become an asset (or as Cal Newport calls it, career capital) in the future.&lt;/p&gt;
&lt;p&gt;Remember, the requirement is to work at least 20 hours a week. Since you’re working for free in many occasions, it’s definitely possible to negotiate your hours and reserve the rest of the time devoting it towards your job searching effort.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#the-first-job-is-always-the-hardest&#34;&gt;
    &lt;h2 id=&#34;the-first-job-is-always-the-hardest&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;The first job is always the hardest&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Don’t take this from me. Take this from almost every other person in the industry I’ve ever met. Get an internship whenever you can! Even if this means you might have to go back to your home country or work on some shitty project. If you are like me, only realizing the importance of this in your senior year, start working on side projects or open source projects. Develop niche, specific skills and people skills. Few months ago, I wouldn’t have known that my experience in developing an Alexa skill (because why not?) would helped me that much in my interview. If you know you won’t be able to impress recruiters with your experience, grades or anything, you’ll need to make it up with something else. Remember, you’ve got a lot more to prove, more than your peers with no visa issues.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://binhong.me/blog/img/bl2.jpeg&#34; target=&#34;_blank&#34;&gt;
    
    &lt;img src=&#34;https://binhong.me/blog/2018-02-10-being-lucky//blog/img/bl2.jpeg&#34; alt=&#34;Photo by Michał Parzuchowski on Unsplash&#34;&gt;
    
&lt;/a&gt;
&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#being-lucky&#34;&gt;
    &lt;h2 id=&#34;being-lucky&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Being lucky&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;I was lucky. I got a phone call from a recruiter asking me if I’m interested in this position. After taking forever to schedule an interview, it turns out the team needed a few more engineers (who knows about ML and speaks Malay ‘natively’) so I was pretty much hired right away. The job offer fell from the sky. It came from somewhere I least expected.&lt;/p&gt;
&lt;p&gt;This is not just me. Almost every time I come across an international student getting an offer for a position at a tech giant for the first time, its always about ‘I was lucky’ somewhere down the story. When people hear that, they rule out that entire story as anecdotal but I think this is a consistent pattern worth exploring. Everyone gets lucky once in a while, but are you ready when your luck strikes? If you aren’t, no matter how many final round interviews magically fall onto your lap, you will fail all of them.&lt;/p&gt;
&lt;p&gt;One could argue that if you are good enough, luck is irrelevant. That’s probably true. But then if you are that good, you are probably just sitting at home debating which offer to pick up instead of sitting here reading this piece of article from some random guy on the internet. It’s not just about being lucky, its also about being ready enough to fully utilize that moment of luck.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href=&#34;https://blog.binhong.me/being-lucky-ce014cafa627&#34;&gt;my personal Medium publication&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
    <item>
      <title>I could become you tomorrow and no one will know</title>
      <link>https://binhong.me/blog/2017-11-20-i-could-become-you-tomorrow-and-no-one-would-find-out/</link>
      <pubDate>Mon, 20 Nov 2017 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2017-11-20-i-could-become-you-tomorrow-and-no-one-would-find-out/</guid>
      <description>&lt;a class=&#34;anchor&#34; href=&#34;#backstory&#34;&gt;
    &lt;h2 id=&#34;backstory&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Backstory&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;I lied, but maybe someone else could. If you didn’t know already, &lt;a href=&#34;https://www.lowyat.net/2017/145654/personal-data-millions-malaysians-sale-source-breach-still-unknown/&#34;&gt;Malaysia just suffered possibly its largest data breach ever&lt;/a&gt; with personal data of millions of Malaysians (including your full name, IC number, phone number and home address) up for grabs as long as the price is right. If you are not freaked out yet, you should be and I’ll tell you why. I don’t think many people understand how dangerous these data can be. I’m also honestly mildly infuriated about this. No, not about the creator of &lt;a href=&#34;https://sayakenahack.com/&#34;&gt;sayakenahack.com&lt;/a&gt; (hereafter referred as Keith) but rather how this whole thing panned out. I’ve mentioned before in private conversations with friends about my concerns on cybersecurity in Malaysia but never really gave it too much thoughts. Not until I discovered this whole fiasco.&lt;/p&gt;
&lt;p&gt;Let’s face it, with the amount of telecommunication companies being breached, it’s fair to assume that anyone who registered a phone number with their personal info on or before 2014 have their personal information leaked. &lt;em&gt;(This was also the general consensus of anyone who had seen the raw data, I didn’t.)&lt;/em&gt; By the time you are reading this article, &lt;a href=&#34;https://www.keithrozario.com/2017/11/sayakenahack-com-answering-the-questions.html&#34;&gt;the site should have already been taken down&lt;/a&gt; due to &lt;a href=&#34;https://www.lowyat.net/2017/147967/mcmc-blocks-sayakenahack-com/&#34;&gt;MCMC blocking it&lt;/a&gt;. (This does not however, means that the data is gone. Plenty of shady individual everywhere still have a copy of all these dangerous piece of information.) I waited for it to be taken down before sharing this as I will cover an &lt;em&gt;exploit&lt;/em&gt; that can possibly end up producing some potentially damaging information with some very basic scripting skills (less than 1 hour of Googling).&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#the-true-risk&#34;&gt;
    &lt;h2 id=&#34;the-true-risk&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;The true risk&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;When I brought this up to some of my friends, their reactions were rather “Meh, I don’t have anything of worth anyway. Why would they wanna hack me?”. This is where you’re wrong. They don’t have to hack you, you’re ALREADY HACKED. As to what can they do with it? &lt;strong&gt;Identity theft&lt;/strong&gt;, basically THEY CAN BECOME YOU. With that, I mean they can apply for loans from banks on your behalf, register phone numbers (to run shady business) on your behalf, essentially enjoy all the benefits of being you without any of the drawbacks.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://binhong.me/blog/img/icImage2.jpeg&#34; target=&#34;_blank&#34;&gt;
    
    &lt;img src=&#34;https://binhong.me/blog/2017-11-20-i-could-become-you-tomorrow-and-no-one-would-find-out//blog/img/icImage2.jpeg&#34; alt=&#34;Information obtainable from SPR website&#34;&gt;
    
&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;When I first read about the breach, I thought, &amp;ldquo;it’s been 3 years now, as long as you’ve moved and changed phone number, they pretty much &amp;ldquo;only&amp;rdquo; have your IC no. It’s bad, but maybe not as severe as I initially might imagined”. Could I have been any more wrong. If you are a registered voter and your IC no is fallen into the wrong hands, whoever this is can easily pull up your full profile (full name, home address) from the SPR website. And while doing so, every single device that this set of data goes through (router, ISP, some surveillance services, or even a MIM attack) gets to see all of these information as well since the site is not encrypted. (It’s 2017 and TLS is free!)&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#responses&#34;&gt;
    &lt;h2 id=&#34;responses&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Responses&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;While I don’t think Keith’s approach is perfect (minor things like captcha, API dealing with non-existent query etc), I still think that is the closest solution available which is both informative (educational) and maintain minimal harm to the victims.&lt;/p&gt;
&lt;p&gt;With MCMC shutting it down, I really hope that they are actually aware of the risk and provide a much better solution (considering that’s what their job is). To all the people out there calling this as a potential phishing, the website ask for numbers where you may or may not put in an actual existing NRIC and does not ask for anything else. If this person were to phish for NRIC, he might as well brute force the SPR website which comes with 100% accuracy and all your personal information.&lt;/p&gt;
&lt;p&gt;And then there is Lowyat.net, no, the data is not in any way manipulated as it was simply &lt;strong&gt;masked&lt;/strong&gt; for the safety of the victims because as you said it yourself, &lt;em&gt;&lt;a href=&#34;https://www.themalaysianinsight.com/s/23152/&#34;&gt;“the sheer amount of information on the site could subject it to abuse”&lt;/a&gt;&lt;/em&gt;. I think you mean raw data not the masked data. Now that’s just a cheap shot at the choice of words, I read through some of the threads in the Lowyat forum and it seems like they are working closely with MCMC to ‘fix’ this. Hopefully, we will see something soon. As for now, I still stand by Keith on the ‘rights to know’ for the victims.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#technical-considerations&#34;&gt;
    &lt;h2 id=&#34;technical-considerations&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Technical considerations&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;&lt;em&gt;Feel free to skip this if you are not into the technical exploitation&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;That said, it has come to my attention* that the API allows easy data scraping. Remember I mentioned an exploit in the beginning? This is it. By knowing the birthday and birth state of an individual, it is possible to do a targeted background search through scraping with the API. While this approach might not be efficient if your intention was to do a mass scrape of all the data &lt;a href=&#34;https://www.keithrozario.com/2017/11/sayakenahack-com-answering-the-questions.html&#34;&gt;as mentioned by Keith&lt;/a&gt;, the limitation in place is more than enough for such targeted digging. I do not want to go into details but if you string all these information I mentioned about throughout the article together, you’ll easily figure out what I am talking about.&lt;/p&gt;
&lt;p&gt;Admittedly, this is less of a threat considering that if you are able to circumvent the captcha used at the SPR site with some sort of image recognition, its easily way worse than any kind of vulnerabilities Keith’s site can be.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;*I didn’t figure this out, credits to this random person who wanted to stay anonymous&lt;/em&gt;&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#after-thoughts&#34;&gt;
    &lt;h2 id=&#34;after-thoughts&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;After thoughts&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Honestly, I’m widely upset by the of public’s reaction or lack thereof. Anyone who got their hands onto such information can just show up and ‘become you’ figuratively tomorrow but you couldn’t care less? I guess I’m going a little too far with my fear-mongering approach but I really think the public awareness of such danger is at little to none.&lt;/p&gt;
&lt;p&gt;Much like the whole Equifax fiasco in the US, I sincerely think this is an important educational opportunity for both the public and the government officials to understand the importance of cybersecurity. I&amp;rsquo;m in no way an expert (not even a beginner so to say) in this realm but the ignorance over such topic is hurting the well-being of not just any individual but the trust in the country. With no punishment in place for organizations mishandling such damaging information (that allows impersonation), how am I ever going to trust anyone, any company, with any of my personal details anymore?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href=&#34;https://blog.binhong.me/i-could-become-you-tomorrow-and-no-one-would-find-out-c9928e915c70&#34;&gt;my personal Medium publication&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
    <item>
      <title>Use Terminal in Windows with Style</title>
      <link>https://binhong.me/blog/2017-08-15-use-terminal-in-windows-with-style/</link>
      <pubDate>Tue, 15 Aug 2017 00:00:00 -0800</pubDate>
      <author>binhong@binhong.me (BinHong Lee)</author>
      <guid>https://binhong.me/blog/2017-08-15-use-terminal-in-windows-with-style/</guid>
      <description>&lt;p&gt;When the Linux Shell in Windows was first released, I used it and didn’t quite like it. Fast forward a year later, I heard it is a lot better now (also the fact that my familiarity with terminal commands improved helped) so I decided to give it another try. It is a really fun experience so far. In case you haven’t install the Windows Subsystem for Linux (WSL), follow this &lt;a href=&#34;https://msdn.microsoft.com/en-us/commandline/wsl/install_guide&#34;&gt;step-by-step guide&lt;/a&gt; by Microsoft to get started. Then, install Hyper for Windows from &lt;a href=&#34;https://releases.hyper.is/download/win&#34;&gt;here&lt;/a&gt;. If you didn’t already, you will also need node.js for the package management for plugins of Hyper.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#opening-the-config-file&#34;&gt;
    &lt;h2 id=&#34;opening-the-config-file&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Opening the config file&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;At the Hyper window, press &lt;code&gt;Ctrl + ,&lt;/code&gt;. It should open the config file “.hyper.js” with your default text editor. In case it throws an error, it is probably because Windows is trying to execute the js file instead of opening it for editing. You have 2 ways to work around this.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;(Not recommended)&lt;/strong&gt; If you have an editor that can be opened through Powershell, then you can go ahead and do &lt;code&gt;atom .hyper.js&lt;/code&gt; (or whatever text editor yours is).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;(Recommended)&lt;/strong&gt; Navigate to the user folder with your File Explorer (“C:\Users%YourUserName%\”). Right click “.hyper.js”, select “Properties”, change the “Opens with: ” to a text editor like ‘Notepad’ etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After this, you should be able to pull up the config file easily with either pressing &lt;code&gt;Ctrl + ,&lt;/code&gt; or through the Hamburger Menu into ‘Edit’ → ‘Preferences’.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#pointing-hyper-to-the-linux-shell&#34;&gt;
    &lt;h2 id=&#34;pointing-hyper-to-the-linux-shell&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Pointing Hyper to the Linux Shell&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;&lt;a href=&#34;https://binhong.me/blog/img/utImage1.png&#34; target=&#34;_blank&#34;&gt;
    
    &lt;img src=&#34;https://binhong.me/blog/2017-08-15-use-terminal-in-windows-with-style//blog/img/utImage1.png&#34;&gt;
    
&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;From the config file, look for the &lt;code&gt;shell: &#39;&#39;&lt;/code&gt;, option as seen from the screenshot above, change it to &lt;code&gt;shell: &#39;C:\\Windows\\System32\\bash.exe\&#39;&lt;/code&gt;, as suggested by the comment. In my personal experience so far, neither keeping nor removing ‘shellArgs’ make any changes to the bash shell. While you are at it, feel free to add any kind of plugins you want to it. Personally, I added &lt;code&gt;&#39;hyperterm-cobalt2-theme&#39;&lt;/code&gt; because I like the theme.&lt;/p&gt;
&lt;p&gt;You should now be able to use Hyper just like a normal Linux Shell.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#general-development-environment-and-tools&#34;&gt;
    &lt;h2 id=&#34;general-development-environment-and-tools&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;General development environment and tools&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Since you will most likely be using this for development, you should definitely run the following commands to set yourself up.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo apt-get update
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo apt-get upgrade
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo apt-get install build-essential
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo apt-get install python-pip
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;pip install --upgrade pip
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Among all of the above, installation of python-pip is crucial to the next step in installing Powerline for Hyper (or more like bash).&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#installing-powerline&#34;&gt;
    &lt;h2 id=&#34;installing-powerline&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Installing Powerline&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#this-section-is-referenced-from&#34;&gt;
    &lt;h4 id=&#34;this-section-is-referenced-from&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;&lt;em&gt;This section is referenced from &lt;a href=&#34;http://iamnotmyself.com/2017/04/15/setting-up-powerline-shell-on-windows-subsystem-for-linux/&#34;&gt;SETTING UP POWERLINE SHELL ON WINDOWS SUBSYSTEM FOR LINUX&lt;/a&gt;&lt;/em&gt;&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h4&gt;
&lt;/a&gt;
&lt;p&gt;Run the following to install Powerline.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; ~
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git clone https://github.com/banga/powerline-shell.git
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; powerline-shell
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cat config.py.dist &amp;gt; config.py
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;./install.py
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;WARNING: you should not at anytime remove &lt;code&gt;powerline-shell&lt;/code&gt; folder (or attempt to) without reverting the following changes that you will make to the &lt;code&gt;.bashrc&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; ~
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;nano .bashrc
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Feel free to use any other command line editor if you prefer (I use vim personally). Add the following lines to the file.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;function&lt;/span&gt; _update_ps1&lt;span class=&#34;o&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;PS1&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;~/powerline-shell/powerline-shell.py &lt;span class=&#34;nv&#34;&gt;$?&lt;/span&gt; 2&amp;gt; /dev/null&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$TERM&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; !&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;linux&amp;#34;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;PROMPT_COMMAND&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;_update_ps1; &lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$PROMPT_COMMAND&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;em&gt;*Note: If you happen use nano (like the given example), do note that &lt;code&gt;Ctrl+X&lt;/code&gt; will not exit the program. You can do &lt;code&gt;F2&lt;/code&gt; instead.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Save and restart Hyper (or refresh) and it should look something like this. Realize the missing characters which are supposed to be the arrows.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://binhong.me/blog/img/utImage2.png&#34; target=&#34;_blank&#34;&gt;
    
    &lt;img src=&#34;https://binhong.me/blog/2017-08-15-use-terminal-in-windows-with-style//blog/img/utImage2.png&#34;&gt;
    
&lt;/a&gt;
&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#fonts-for-powerline&#34;&gt;
    &lt;h3 id=&#34;fonts-for-powerline&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Fonts for Powerline&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h3&gt;
&lt;/a&gt;
&lt;p&gt;For the character to show, you will need Powerline fonts installed and set it in your config file &lt;code&gt;Ctrl + ,&lt;/code&gt; for Hyper.&lt;/p&gt;
&lt;p&gt;Go to &lt;a href=&#34;https://github.com/powerline/fonts&#34;&gt;https://github.com/powerline/fonts&lt;/a&gt;, pick a font you like (I use monofur) or clone the repo and install all the fonts. Remember to install it as Windows, meaning you will be running the ps1 file (PowerShell) instead of the sh file. If it throws an error saying something along the lines of “running scripts is disabled”, follow the instructions &lt;a href=&#34;https://www.faqforge.com/windows/windows-powershell-running-scripts-is-disabled-on-this-system/&#34;&gt;here&lt;/a&gt; to enable it and run it again.&lt;/p&gt;
&lt;p&gt;When you are done with the installation, remember to change the font through the config file, then reload Hyper. At this point, it should look something like the following screenshot.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://binhong.me/blog/img/utImage3.png&#34; target=&#34;_blank&#34;&gt;
    
    &lt;img src=&#34;https://binhong.me/blog/2017-08-15-use-terminal-in-windows-with-style//blog/img/utImage3.png&#34;&gt;
    
&lt;/a&gt;
&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#optional-installing-npm&#34;&gt;
    &lt;h2 id=&#34;optional-installing-npm&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;(Optional) Installing npm&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#this-section-is-referenced-from-1&#34;&gt;
    &lt;h4 id=&#34;this-section-is-referenced-from-1&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;&lt;em&gt;This section is referenced from &lt;a href=&#34;https://randika.com/installing-nvm-nodejs-and-npm-on-ubuntu-f6deef0cd22a&#34;&gt;Installing nvm, nodejs and npm on Ubuntu&lt;/a&gt; by &lt;a href=&#34;https://medium.com/@randika&#34;&gt;Randika Rathugamage&lt;/a&gt;&lt;/em&gt;&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h4&gt;
&lt;/a&gt;
&lt;p&gt;The obvious way to do this is by installing it through the default package manager. However, the npm installed through apt-get is 3.5.2 version without node. I personally prefer installing it with nvm starting by cloning the nvm repository and adding it to &lt;code&gt;.bashrc&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; ~
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git clone https://github.com/creationix/nvm.git
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;source ~/nvm/nvm.sh&amp;#34;&lt;/span&gt; &amp;gt;&amp;gt; .bashrc
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;From here, you can either reset Hyper or do a &lt;code&gt;source .bashrc&lt;/code&gt; to get nvm working for you. After that, you can do &lt;code&gt;nvm ls-remote&lt;/code&gt; to look at all the versions available before deciding which to install or you can just do &lt;code&gt;nvm install node&lt;/code&gt; and call it a day.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#optional-configuring-aliases&#34;&gt;
    &lt;h2 id=&#34;optional-configuring-aliases&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;(Optional) Configuring aliases&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;It is important to note that the Linux file system is separated from the Windows file system (located in C:/User/your-name/AppData/Local/lxss). Windows went as far as to not show the folder in File Explorer even if you select to show hidden files just so you don’t mess with it. In one scenario where I made a change to one of the files in there from File Explorer through my text editor in Windows, the WSL no longer recognizes the existence of such file in the directory immediately.* This is why you should keep all your files in your Windows side of file system and access them through something like cd /mnt/e/git/. Retyping the file path over and over again can be annoying so instead, you can set an alias by adding the command below but changing the location to the directory where you store your development files.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;*Workaround: I ended up creating a new file with vim in bash and copy pasting the entire existing file from Windows. I also renamed the files beforehand to prevent file conflict.&lt;/em&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;echo&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;alias gitfiles=&amp;#34;cd /mnt/e/git/&amp;#34;&amp;#39;&lt;/span&gt; &amp;gt;&amp;gt; ~/.bashrc
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;From here on out, you can just do &lt;code&gt;gitfiles&lt;/code&gt; in Hyper (or bash) and you will go directly to your development folder.&lt;/p&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#reset&#34;&gt;
    &lt;h2 id=&#34;reset&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Reset&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#this-section-is-referenced-from-2&#34;&gt;
    &lt;h4 id=&#34;this-section-is-referenced-from-2&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;&lt;em&gt;This section is referenced from &lt;a href=&#34;https://superuser.com/questions/1065569/how-to-remove-reset-windows-subsystem-for-linux-on-windows-insider-build-14316&#34;&gt;How to remove/reset Windows Subsystem for Linux on Windows Insider Build 14316&lt;/a&gt;&lt;/em&gt;&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h4&gt;
&lt;/a&gt;
&lt;p&gt;If at any point, you seems to have screwed up your Linux subsystem, you can always reset it by going to your command prompt &lt;code&gt;cmd.exe&lt;/code&gt; with admin privilege (Run as administrator) and input the following commands to reset the Linux subsystem.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;lxrun /uninstall /full
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;lxrun /install
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;a class=&#34;anchor&#34; href=&#34;#other-useful-links&#34;&gt;
    &lt;h2 id=&#34;other-useful-links&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Other useful links&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://www.webupd8.org/2015/02/install-oracle-java-9-in-ubuntu-linux.html&#34;&gt;Installing Oracle Java for development&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup&#34;&gt;First time Git setup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Setting up ssh access for GitHub (&lt;a href=&#34;https://help.github.com/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent/#platform-linux&#34;&gt;Creating key&lt;/a&gt;, &lt;a href=&#34;https://help.github.com/articles/adding-a-new-ssh-key-to-your-github-account/#platform-linux&#34;&gt;Adding key to account&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.ronakshah.net/Making-Terminal-Great-Again/&#34;&gt;Making Terminal Great Again&lt;/a&gt; (My inspiration to write this)&lt;/li&gt;
&lt;/ul&gt;
&lt;a class=&#34;anchor&#34; href=&#34;#afterthoughts&#34;&gt;
    &lt;h2 id=&#34;afterthoughts&#34;&gt;
        &lt;span class=&#34;text&#34;&gt;Afterthoughts&lt;/span&gt;
        &lt;span class=&#34;tag&#34;&gt;#&lt;/span&gt;
    &lt;/h2&gt;
&lt;/a&gt;
&lt;p&gt;Initially, I did this with &lt;a href=&#34;https://github.com/robbyrussell/oh-my-zsh/wiki/Installing-ZSH&#34;&gt;zsh&lt;/a&gt; (since I personally uses &lt;a href=&#34;https://github.com/bhilburn/powerlevel9k&#34;&gt;Powerlevel9k&lt;/a&gt; on my Mac and I love it) but zsh doesn’t seems to play well with Hyper (specifically with the plugins) causing a lot of weird configuration behaviors. Even without Hyper, the alignments for Powerlevel9k specifically goes way off causing the input line to be on the next line instead. I speculate this is due to zsh not being built with Windows in mind and does not have such compatibility.&lt;/p&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href=&#34;https://blog.binhong.me/use-terminal-in-windows-with-style-45158e0c2f50&#34;&gt;my personal Medium publication&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
  </channel>
</rss>
