<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Stanley Nguyen on Medium]]></title>
        <description><![CDATA[Stories by Stanley Nguyen on Medium]]></description>
        <link>https://medium.com/@stanleynguyen?source=rss-9508cde08448------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*54Yu8QCoVuSnTrVAflE5XA.jpeg</url>
            <title>Stories by Stanley Nguyen on Medium</title>
            <link>https://medium.com/@stanleynguyen?source=rss-9508cde08448------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 02:31:31 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@stanleynguyen/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Applying Open Source contribution model at Xendit and building a better engineering culture]]></title>
            <link>https://medium.com/xendit-engineering/applying-open-source-contribution-model-at-xendit-and-building-a-better-engineering-culture-852231b4b7dd?source=rss-9508cde08448------2</link>
            <guid isPermaLink="false">https://medium.com/p/852231b4b7dd</guid>
            <category><![CDATA[engineering]]></category>
            <category><![CDATA[open-source]]></category>
            <category><![CDATA[engineering-culture]]></category>
            <dc:creator><![CDATA[Stanley Nguyen]]></dc:creator>
            <pubDate>Thu, 20 May 2021 03:02:28 GMT</pubDate>
            <atom:updated>2021-05-20T03:02:28.531Z</atom:updated>
            <content:encoded><![CDATA[<h4>How the OpenSource model can work for InnerSource too!</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RfMzs-khF5LhsFH_36_ztw.jpeg" /></figure><p>The prevalence of OpenSource software is undeniable. If a software developer tells us that he/she has never do anything involving OpenSource Software (OSS), we’re probably looking at a time traveler from when the Internet didn’t exist!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/659/0*WESv7a4OmYqxlmEt.jpg" /><figcaption>There’re only 2 types of developers who’ve never worked with OSS</figcaption></figure><p><a href="https://opensource.com/article/17/8/enterprise-open-source-advantages">Time</a> and <a href="https://www.outsource2india.com/software/articles/open-source-software.asp">time</a> again, the benefits of adopting and contributing to Open Source Software are highlighted. Nevertheless, there’s little to no literature to how an organisation can properly adopt and contribute to Open Source Software. <a href="https://www.google.com/search?q=how+to+start+contributing+to+open+source+as+an+organization">A quick Google search</a> would mainly show guides on how individuals can get started contributing to Open Source projects without anyone mentioning how we can do it efficiently at an organisational level and build a culture around Open Source.</p><p>This post will outline our attempt at Xendit so far trying to build a model, and in turn — a culture, around Open Source contributing.</p><h3>Challenges at Xendit</h3><p>We didn’t started building a model of contributing to InnerSource out of the blue. It actually came from a real need for us to deal with legacy code.</p><p>It all started with Xendit’s usage of <a href="https://microservices.io/">micro-service architecture</a> with the best of intentions: We want folks to be able to “move fast and break things”, to develop and deliver more features, more payment channels without stepping on one another’s toes.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RKCRpB8mHZCoye2Ouw7ifw.png" /></figure><p>Under this ownership model, there were a lot of common “utils” repositories born out of necessity, without much thoughts and considerations in place, as folks need a quick way to share code across different codebases.</p><p>Fast forward to the next growth stage of our engineering organisation, we have evolved to have proper teams with clear responsibilities and boundaries over specific codebases depending on the product</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gLkblpo0Ts-InQdbuVjCpQ.png" /></figure><p>But this also mean that we end up with a bunch of “orphan” utilities repositories, which are ironically used by almost everyone in their codebases and capable of bringing about critical system issues if errors seep in. We actually got bitten by these problems more than a few times and decided to do something about it.</p><h3>Tackling the “everybody’s business” problem</h3><p>As the saying goes — “Everybody’s business is nobody’s business”, it’s natural, however detrimental and painful, for our folks to leave these critical pieces of our system unattended and only patch them when they break down our systems.</p><p>So we took a step back and a good bird-eye-view look of the whole system. These common modules play somewhat a similar role to external libraries like <a href="https://expressjs.com/">ExpressJS</a>. This inspired us to figure out what we were lacking that made all the difference. We identified 2 missing pieces of the puzzles:</p><ul><li>There’s a lack of recognitions and rewards: Usually for Open Source projects, especially ones that are widely used, to which contribution gives developers “street cred” and respect from their peers. And with recent push in Open Source funding like <a href="https://github.com/sponsors">Github Sponsors</a>, there’re actual monetary incentives to writing free softwares.</li><li>There’s also a lack of ownership: It’s common for Open Source libraries to be started by someone with a rather personal and specific need for their projects, then slowly evolve into a proper maintainer group and sometimes even an entire foundation. In any cases, the projects hit close to home.</li></ul><p>So how can we emulate the same incentives for Xendit common codebases? How should we adapt the Open Source model into our organisation structure.</p><h3>Open Source @ Xendit</h3><p>To solve the above two problems, we came up with a system to reward engineers for their effort and a clear structure for taking on maintainer’s responsibilities.</p><h4>Incentivising contributions</h4><p>Developers are essentially humans and we all respond to incentives. Our current situation means that there’s only an incentives on the negative side — fix common libraries so our applications stay up, and that’s definitely not how quality softwares are built.</p><p>We decided that there must be positive incentives to motive our engineers. The first step to creating rewards is to measuring contributions so we came up with a formula to point the karma for each Github issue. This is the current version after multiple refinements:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/619/1*17JlKj4h1uRPtx-eQKtfAw.png" /></figure><p>According to the model outlined in previous section, Open Source rewards come in two forms — street creds and monetary. We have titles for top contributor of the month — <strong><em>Wizard of OSS</em>, </strong>and for top contributor of the year — <strong><em>Lord of the OSS, </em></strong>both of whom pictures will be up on our internal OSS contribution leaderboard. On top of that, folks can redeem karma points for educational purchases in a progressive manner with the more points accumulated, the more each point worths.</p><h4>Distributed ownership</h4><p>The second piece of the puzzle for our program to be effective is ownership. We started by forming core maintainer groups based on historical contributions to each of the existing common repositories with their own slack handles and responsibilities (<a href="https://giphy.com/gifs/MCZ39lz83o5lC">with great handles come great responsibilities</a>):</p><ul><li>Answer all questions, concerns about the repositories that they are overseeing</li><li>Code reviews for contributions</li><li>Review feature requests</li><li>Initiate new feature discussions towards project vision</li><li>Maintain and publish new versions of the repository</li><li>Nominate, assess and onboard new core maintainers</li></ul><p>This mirrors the same structure that we often observe in Open Source projects out in the wild, with small groups of core maintainers keeping the projects in line with the directions and purposes they were created for. This also means that new common projects within our organisation will have proper ownership (which we initially had a few years back before all the problems) and succession plan (which was what we lack that is the main cause for ownership diminishing).</p><h3>Small wins &amp; future ambitions</h3><p>Ever since its inception, our internal OSS program has:</p><ul><li>Grown our contributions to the organisation’s common codebases from happy-go-lucky to frequent grooming and maintenance by over 30 enthusiastic engineers (about 15% of the total engineering head-counts).</li><li>Helped us cut down the turn-around for bug-fixes and feature releases of our common libraries to within a single week.</li><li>Given us a proper platform to have discussion about organisational standards and move towards more widely accepted industry standards like <a href="https://opentelemetry.io/">OpenTelemetry</a> instead of coming up with and maintaining our own.</li></ul><p>With these foundations set up within our own internal operations, we’re looking to be involved more in Open Source work with the public communities, to be able to give back to whom we’ve benefited so much from.</p><p>We hope that our journey so far could be an inspiration for other organisations to develop Open Source culture with a less steep learning curve.</p><p>Last but not least, if you would like to join us in our Open Source adventure, don’t hesitate because <a href="https://xendit.co/careers?utm_source=Medium&amp;utm_medium=social&amp;utm_campaign=engineering-blog&amp;utm_term=blog-post">Xendit is always looking for awesome people!</a></p><figure><a href="https://xendit.co/careers?utm_source=Medium&amp;utm_medium=social&amp;utm_campaign=engineering-blog&amp;utm_term=blog-post"><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2ZV5eWHo7LGNDtPPaTE-8g.png" /></a></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=852231b4b7dd" width="1" height="1" alt=""><hr><p><a href="https://medium.com/xendit-engineering/applying-open-source-contribution-model-at-xendit-and-building-a-better-engineering-culture-852231b4b7dd">Applying Open Source contribution model at Xendit and building a better engineering culture</a> was originally published in <a href="https://medium.com/xendit-engineering">Xendit Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[My Impossible™ kebab and the economics of green products]]></title>
            <link>https://medium.com/age-of-awareness/my-impossible-kebab-and-the-economics-of-green-products-348a04b5b680?source=rss-9508cde08448------2</link>
            <guid isPermaLink="false">https://medium.com/p/348a04b5b680</guid>
            <category><![CDATA[global-warming]]></category>
            <category><![CDATA[sustainability]]></category>
            <category><![CDATA[environment]]></category>
            <category><![CDATA[economics]]></category>
            <category><![CDATA[product-design]]></category>
            <dc:creator><![CDATA[Stanley Nguyen]]></dc:creator>
            <pubDate>Tue, 05 May 2020 22:31:00 GMT</pubDate>
            <atom:updated>2020-05-05T22:31:00.939Z</atom:updated>
            <content:encoded><![CDATA[<h4>A thought and food experiment from a neutral environmentalist</h4><p>I have never been a fan of vegan food no matter how much information I read, heard, absorbed via social media, or from friends on how vegan products is a more efficient method of converting energy from sunlight for humans. I’m aware of global warming and generally concerned about it. I would not use straws and would bring my own bags to the supermarket, however, completely leaving meat out of my meals and stopping myself from enjoying non-vegan food is something that I’m just not ready to give up.</p><p>So the folks at Impossible™ made “meat” from plants so that people like me can — in their own words — “Eat meat while saving Earth”. I have heard about them for quite some time but haven’t tried one until I noticed that my favourite take-away kebab joint offers Impossible™ kebabs on their menu.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eH1RpUghYtjysimeJOQa6w.png" /><figcaption>Kebab impossible to resist?</figcaption></figure><p>That moment I knew that I had to have Impossible™ kebab for lunch and beef kebab for dinner with the same complement vegetables and condiments so I could compare them.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tDxMfy4J8twPjG2k6Lo2pg.jpeg" /><figcaption>Pardon my un-glam bites but they look quite the same eh?</figcaption></figure><p>Verdict: Impossible™ meat tastes pretty much the same as beef with a slightly different texture that’s easier to disintegrate (definitely would have it again but not every day 😂). This made me question myself why I’d never tried it until then, and in turns, kickstarted my thought into a rabbit hole of modelling our purchasing decision of green products.</p><p>The simple answer to the above mentioned question of why I had never tried Impossible™ and why I decided I would have this again is:</p><ul><li>Availability: I had never seen any of my frequent choices of food offering Impossible™ on their menu until now</li><li>Taste: If it tastes and feels the same as animal-based meat, why not?</li><li>Price: The small difference in price is something I wouldn’t mind compromising on (but an equal or lower price would make me buy more often probably?)</li></ul><p>These factors mentioned above contribute and can be consolidated into a dimension of my model, namely, “convenience”. The other dimension of my model, since we’re talking about green products, can’t be anything else but “eco-friendliness”. I’m hypothesising that these are the 2 major dimensions on which people make a purchasing decision of green products.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/667/1*2vUImvDgtQeXbuOZWmHieA.png" /><figcaption>The bases for our model for economics of green products</figcaption></figure><p>As humans, we have certain limits to our decisions and actions. I wouldn’t pay 10 times the usual price of my kebab nor travel 2 hours away from my place to do my part in curbing food with high carbon footprint. Same goes to eco-friendliness, I wouldn’t eat something that tastes like rainbow 🌈 if it immediately raises Earth’s temperature by a degree. Let’s add these limits to our models using these dotted lines.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/664/1*5WPaXI4ZhM6pFhpERVWQQQ.png" /><figcaption>Left side of the green dotted line and bottom under the red dotted line are non-action zones</figcaption></figure><p>However, as we don’t make our decisions based on these limits alone, being in the top right quadrant doesn’t guarantee we would buy these products. So if it’s not the limit lines, what is our boundary for action vs non-action then? How do we combine both aspects of purchasing decisions into a single function? Which mathematical model would take into account both factors without letting either one overpower the other (i.e. not letting something with extremely high convenience value and extremely low eco-friendliness enter the buy zone)? Our answer is product function of convenience and eco-friendliness. Something like</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/383/1*DOnR5XE0HIxeUi-tU0WNSA.png" /><figcaption>x — eco-friendliness and y — convenience</figcaption></figure><p>In the above function, we have x as the variable for eco-friendliness, and y for convenience. l<em>x</em> and l<em>y</em>, respectively, are the limits for these variables (represented by the green dotted line and the red dotted line in our graph previously). <em>C</em> is a constant that represents how hard a buyer we are. The higher <em>C</em> is, the more it takes for us to make a buy decision (And as human we’re, each one of us would have a different <em>C</em> value based on our personal ideologies)</p><p>With l<em>x</em> and l<em>y</em> as bounds for eco-friendliness and convenience values (i.e. x &gt; l<em>x</em> and y &gt; l<em>y</em>), we can plot the boundary of the action zone like so — with the boundary (purple line) approaching the limit lines as we go further up or right the graphs but never converging with them (checkout <a href="https://en.wikipedia.org/wiki/Limit_(mathematics)">this Wiki</a> if you’d love to find out more).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/666/1*OuCkoe0Qt0Z-QkxCAkGxCA.png" /><figcaption>I reckon this is the kind of curve that mathematicians find sexy</figcaption></figure><p>Now let’s apply this model to my meals and see where them kebabs are. Since both of my kebabs are definitely in the buy-zone, for demonstration purpose, I would add in raw beef for cooking — something which would hardly make it to my buy zone as I find cooking too much of a hassle.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/669/1*nb7ftnhdDSzIChe1FcwqJA.png" /><figcaption>Possibilities of my meals</figcaption></figure><p>As we can see from the graph, Impossible™kebab is less “convenient” compared to my normal beef kebab with its higher price but its higher value of eco-friendliness kept it in the buy-zone for me. Raw-beef-for-cooking, despite being in the top-right quadrant — beating both my limits for convenience and eco-friendliness — didn’t make it to the buy zone as its combined value of convenience and eco-friendliness is not enough to get me to action.</p><h4>The case study of electric cars</h4><p>Now rewind back to the year 2001, when GM released their electric car model EV1, Stan decided to buy one and was getting all pumped up to amaze his friend Joe about this car and how eco-friendly a person he was.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kOq28I14JtGJS0v8vmoPqA.png" /><figcaption>But..But.. It’s eco-friendly</figcaption></figure><p>Stan didn’t even manage to tell Joe that it’s environmentally friendly! And GM EV1 did really <a href="https://en.wikipedia.org/wiki/General_Motors_EV1#Cancellation">prove to be pretty impractical (50mph top speed and 80 miles range full charge) and too expensive to be commercially viable</a>.</p><p>Fast-forward to 2012, this time our environmentalist Stan managed to be one of the first people to get himself a newly delivered Tesla model S. Here he is showing Joe his new car.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*q5ftr342-t9UbljgYXhm4g.png" /><figcaption>Joe was truly impressed this time round!</figcaption></figure><p>Stan finally had his chance to proudly show off his eco-friendly car with curves as sexy as any other gasoline-powered cars. Going beyond our imaginary story of Stan and Joe, Tesla cars actually compete heads-on with gasoline-powered cars, racking in practical numbers for top speed and range while having eye-catching designs (fun fact: Tesla’s first 4 models’ names combined is “S3XY” if you know what Elon means 😉). On top of that, Tesla managed to build <a href="https://www.tesla.com/supercharger">a dense network of supercharger stations</a>, making it a breeze to charge their cars. These are the reasons why Tesla cars sell like hotcakes. So which EV is more likely to save the planet? A model with eco-friendliness as a secondary selling point or one with the major (or sole?) selling point to be environmental friendliness?</p><p>Let’s apply our model on these EVs with the additions of a normal gasoline-powered sedan from Ford (white colour) and a hybrid car model from Toyota (grey colour). We should get something like this</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/668/1*Z99sseqqlZbJYTreboYnnQ.png" /><figcaption>Clear winner for Tesla?</figcaption></figure><p>From the rough graph that I plot above, doesn’t it look like Tesla is clearly winning the race against less eco-friendly counterparts because it’s not much less “convenient”? However, Tesla EVs (or other commercial EVs on the market) are not yet clearly superior to their gasoline-powered alternatives. Hence, for some people with a higher convenient limit (i.e. due to a smaller budget, or living in a country where there’s no supercharger station, etc), they might still opt for a gasoline-power car — scoring one point for team “Global Warmers”.</p><h4>Messages to the green economy influencers</h4><p>Although this is just a fun and random thought experiment of mine (mathematicians and economists please spare my mistakes if there’re any 😅), I hope that it can prompt the influencers of our future green economy to think in multi-dimension.</p><p>My hope and encouragement are with product designers and engineers who love to pave the way to a sustainable future to not be discouraged if your green products has not been accepted by consumers. Keep refining, reiterating and pushing the convenience scores of your products up.</p><p>My hope is with environmental activists to be more <a href="https://www.bloomberg.com/opinion/articles/2019-08-04/greta-thunberg-it-s-ok-to-fly-to-new-york">multi-dimensional in your advocacy,</a> instead of advocating to push a single dimension of the equation to a ridiculously off-limit point for a majority of people because that won’t be effective as no one is able to listen to your advice (Just like how <a href="https://www.factcheck.org/2019/06/trump-bungles-climate-change-in-uk/">sensible people wouldn’t listen to Donald Trump on climate change</a>). Explore alternatives, learn about sustainable solutions if you have to and advocate with the right mission.</p><p>My hope is with the mass consumers to adapt to the uncertain future, to adjust your limits, to go beyond our pampering habits of unsustainability, and be more accepting of green products and green lifestyles. The only way out of global warming is a global effort.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=348a04b5b680" width="1" height="1" alt=""><hr><p><a href="https://medium.com/age-of-awareness/my-impossible-kebab-and-the-economics-of-green-products-348a04b5b680">My Impossible™ kebab and the economics of green products</a> was originally published in <a href="https://medium.com/age-of-awareness">Age of Awareness</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building my Github Action to deliver doggoes to PRs]]></title>
            <link>https://medium.easyread.co/building-my-github-action-to-deliver-doggoes-to-prs-d9cd5d4a42d0?source=rss-9508cde08448------2</link>
            <guid isPermaLink="false">https://medium.com/p/d9cd5d4a42d0</guid>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[github-actions]]></category>
            <category><![CDATA[dogs]]></category>
            <category><![CDATA[continuous-delivery]]></category>
            <category><![CDATA[github]]></category>
            <dc:creator><![CDATA[Stanley Nguyen]]></dc:creator>
            <pubDate>Sun, 08 Mar 2020 18:43:00 GMT</pubDate>
            <atom:updated>2020-03-10T04:37:25.086Z</atom:updated>
            <content:encoded><![CDATA[<h3>Building My Github Action to Deliver Doggoes to PRs</h3><h4>Because who doesn’t want good boys to show up whenever they push?</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/500/0*Mj4QtH-swcMFRMkB.png" /></figure><p>The long awaited GitHub Action feature is finally out of beta and ready to be present in every repositories. GitHub even organized <a href="https://githubhackathon.com/">a Hackathon</a> through out March to encourage folks to create more awesome and useful actions. While browsing through the submissions, I found <a href="https://github.com/ruairidhwm/action-cats">a cool GitHub Action</a> that posts cat gifs on pull requests. Shout out to <a href="https://ruairidh.dev/">Ruairidh</a> with his cool idea 👏👏👏.</p><p>It’ll only do justice to dogs if there’s an action that deliver them good boys to our PRs. That moment I knew exactly what my next Github Action project would be. Time to get to work.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/500/0*82BIm07p2L2cgXWo" /></figure><h3>The Gist of Creating a GitHub Action</h3><p><a href="https://help.github.com/en/actions/getting-started-with-github-actions/about-github-actions">GitHub actions</a> are basically premade (with ❤️) units of work to be used GitHub workflows (think <a href="https://travis-ci.org/">Travis</a>’s builds). Github actions can be either built with <a href="https://help.github.com/en/actions/building-actions/creating-a-docker-container-action">Docker containers</a> or <a href="https://help.github.com/en/actions/building-actions/creating-a-javascript-action">JS/TS scripts</a>. An advantage to creating a GitHub action with JS/TS is readily available modules from <a href="https://github.com/actions/toolkit">GitHub toolkit</a>. With such integration support, it’s much easier to connect with GitHub services (C’mon 🙄 Who wanna write <strong>curl</strong> commands to make API calls). It&#39;s obvious to go with <a href="https://github.com/actions/typescript-action">TypeScript action template</a>. With that decision made, let&#39;s get down to writing <a href="https://github.com/stanleynguyen/action-dogs">action-dogs</a>.</p><h4>The Main Run File</h4><p>In a JS/TS GitHub action, workload will be started out from a main entrypoint (think running <strong>node src/index.js</strong> to start a Node process for web applications, etc). For action-dogs, this is my basic setup for the main program</p><pre>import * as core from &quot;@actions/core&quot;;<br>import * as github from &quot;@actions/github&quot;;<br>import { generate } from &quot;./doggo/generator&quot;;</pre><pre>(async function run(): Promise&lt;void&gt; {<br>  try {<br>    const ctx = github.context;<br>    if (!ctx.payload.pull_request) {<br>      throw new Error(&quot;Not in the context of a PR!&quot;);<br>    }</pre><pre>    const ghCli = new github.GitHub(core.getInput(&quot;github-token&quot;));<br>    const doggo = generate();<br>    ghCli.issues.createComment({<br>      ...ctx.repo,<br>      issue_number: ctx.payload.pull_request.number,<br>      body: `![Doggo](${doggo})`<br>    });<br>  } catch (e) {<br>    core.setFailed(e.message);<br>  }<br>})();</pre><p>During an event that can trigger GitHub workflows, we are provided with a context object that can be accessed through <strong>@actions/github</strong> module. Using that, I&#39;m able to check whether my payload is coming from a <strong>pull_request</strong> and reject otherwise. Next up, I need to post a comment to the corresponding pull request with content of a doggo gif. Given that my doggo generator (of which I will explain in the next section) works properly, I can retrieve an URL of a doggo gif, creating a comment on pull requests is super simple as I just need to pass in the repo&#39;s information from our context object and the PR&#39;s number. Also, in case we get any errors during these operations, calling <strong>core.setFailed(e.message)</strong> will mark the build as failed with the error message.</p><h4>The Doggo Generator</h4><p>After much research with the intention to use one of the public APIs to get random doggo gifs, I couldn’t find one that’s public (like <a href="https://dog.ceo/">dog.ceo</a>) and also serves gifs (like <a href="https://giphy.com/">GIPHY</a>). Since there’s no avenue for me to store my GIPHY API key securely to be used in action-dogs, I fell back to the good-ol-way of static JSON array.</p><p>Wanna know how I obtained my array full of doggo awesomeness (from ❤️ <a href="https://giphy.com/">GIPHY</a> ❤️) without any API key generated in the process? I actually went to GIPHY site, searched for doggoes, and scrolled down util a good amount of “giffy-boys” got rendered before pulling up my console</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/601/0*0c-CpL2TSE8cgIul" /></figure><p>And with these few lines of JS</p><pre>const dogsData = [];<br>document<br>  .querySelectorAll(&quot;a._2SwDiFPqIlZmUDkxHNOeqU&quot;)<br>  .forEach(e =&gt; dogsData.push(e.href));<br>var dataStr =<br>  &quot;data:text/json;charset=utf-8,&quot; +<br>  encodeURIComponent(JSON.stringify(dogsData));<br>var dlAnchorElem = document.createElement(&quot;a&quot;);<br>dlAnchorElem.setAttribute(&quot;href&quot;, dataStr);<br>dlAnchorElem.setAttribute(&quot;download&quot;, &quot;dogs.json&quot;);<br>dlAnchorElem.click();</pre><p>which basically grab `href` values from all “copy link” elements on the search results page, stream them to a JSON array and fill in a file for me to “download”, “generating” is simply picking a random URL from the array.</p><pre>import dogs from &quot;./dogs.json&quot;;</pre><pre>export function generate(): string {<br>  return dogs[Math.floor(Math.random() * dogs.length)];<br>}</pre><h4>Testing</h4><p>I wrote a unit test for my doggo generator using jest (but actually mainly as an avenue for funny descriptions).</p><pre>import { generate } from &quot;../../src/doggo/generator&quot;;</pre><pre>describe(&quot;doggo generator&quot;, () =&gt; {<br>  test(&quot;to return a good boy&quot;, () =&gt; {<br>    Math.random = jest.fn().mockReturnValue(0);<br>    const good = &quot;https://media3.giphy.com/media/mCRJDo24UvJMA/giphy.gif&quot;;<br>    const boy = generate();<br>    expect(boy).toBe(good);<br>  });<br>});</pre><p>But the real test is a workflow using <strong>action-dogs</strong> itself (Yes, you can use a GitHub action on its own repo 🤯).</p><pre>name: &quot;doggo&quot;</pre><pre>on: pull_request</pre><pre>jobs:<br>  build:<br>    runs-on: ubuntu-latest<br>    steps:<br>      - uses: actions/checkout@v2<br>      - uses: stanleynguyen/action-dogs@master<br>        with:<br>          github-token: ${{ secrets.GITHUB_TOKEN }}</pre><p>Now I can see <strong>action-dogs</strong> in action with <a href="https://github.com/stanleynguyen/action-dogs/pull/1">a sample PR</a>. Hurray 🙌🙌🙌!! Now I can safely <a href="https://help.github.com/en/actions/building-actions/publishing-actions-in-github-marketplace">publish it on GitHub Marketplace</a>.</p><h3>Outtro</h3><p>So that’s my story of creating <strong>action-dogs</strong> for fun and learning. You can find source code right on <a href="https://github.com/stanleynguyen/action-dogs">GitHub</a> (well, because where else could it be 🤷‍♂️) and <strong>action-dogs</strong> on <a href="https://github.com/marketplace/actions/action-dogs">Marketplace</a>.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fupscri.be%2Fc36271%3Fas_embed%3Dtrue&amp;dntp=1&amp;display_name=Upscribe&amp;url=https%3A%2F%2Fupscri.be%2Fc36271&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=upscri" width="800" height="400" frameborder="0" scrolling="no"><a href="https://medium.com/media/1f83e4a733ad3206f47e6dd38aa4fc6d/href">https://medium.com/media/1f83e4a733ad3206f47e6dd38aa4fc6d/href</a></iframe><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d9cd5d4a42d0" width="1" height="1" alt=""><hr><p><a href="https://medium.easyread.co/building-my-github-action-to-deliver-doggoes-to-prs-d9cd5d4a42d0">Building my Github Action to deliver doggoes to PRs</a> was originally published in <a href="https://medium.easyread.co/">Easyread</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Quick terminal-based DNS switching]]></title>
            <link>https://medium.easyread.co/quick-terminal-based-dns-switching-4ffa407dfd38?source=rss-9508cde08448------2</link>
            <guid isPermaLink="false">https://medium.com/p/4ffa407dfd38</guid>
            <category><![CDATA[privacy]]></category>
            <category><![CDATA[terminal]]></category>
            <category><![CDATA[dns]]></category>
            <category><![CDATA[network]]></category>
            <category><![CDATA[command-line]]></category>
            <dc:creator><![CDATA[Stanley Nguyen]]></dc:creator>
            <pubDate>Tue, 29 Oct 2019 08:18:52 GMT</pubDate>
            <atom:updated>2019-10-29T11:33:48.479Z</atom:updated>
            <content:encoded><![CDATA[<h4>A quick guide to switching DNS servers settings to tackle nameserver blocking &amp; corporate proxy</h4><figure><img alt="cover image" src="https://cdn-images-1.medium.com/max/712/1*Z6GF1jXn4HN7SnQX175YBw.png" /></figure><h3>Motivation</h3><p>Most of us, at some point in our lives, have used at least a custom DNS server configurations for our computers. Be it for avoiding censorship, faster browsing, security, or bypassing the content restriction, we’ve all tried setting our DNS servers to some public DNS servers like one from Google (remember 8.8.8.8 and 8.8.4.4?). I myself is also no exception as I have been using <a href="https://1.1.1.1/dns/">1.1.1.1</a> for privacy and speed. Unfortunately, having custom DNS nameservers also comes with some setbacks for someone who uses their computer all the time in all the different places. With custom DNS servers, this is what you get on a network that tries to block them (together with VPN services etc.)</p><figure><img alt="screenshot of login page not being able to load" src="https://cdn-images-1.medium.com/max/1024/1*snJ_Xlfdi86okKvvxIcY2g.png" /></figure><p>Another problem that we would have with custom DNS servers is that networks’ login pages just don’t show up as the default DNS is not being used to redirect us to the local network login page. Of course, it would be easy to just remove the configurations as you (most likely) was the one who set them up, how hard would it be to remove? But what if we need these alternate DNS servers again? I personally find this way of manually removing and adding them back too much of a hassle. Hence, I wrapped them in a bash function that does the job for me as all lazy programmers do.</p><h3>1.1.1.1</h3><p>Before starting with explaining what I did in the next section, I would like to do a big shout-out to this awesome project that’s a combined effort from <a href="https://www.cloudflare.com/">Cloudflare</a> and <a href="https://www.apnic.net/">APNIC</a> — <a href="https://1.1.1.1/dns/">1.1.1.1</a>. It’s an effort to provide Internet users with a faster and more secure DNS (it is more than 2x faster than Google Public DNS 🚀🚀🚀). But more than just that, it is a <strong>privacy-first</strong> DNS server. Did you think only these companies like Google, Facebook trying to track all of your activities online (which can be blocked with the awesome <a href="https://www.eff.org/privacybadger">Privacy Badger</a>)? Your ISP also eavesdrops on whichever site you are visiting and sell these data to target you with ads, and they do it through, guess what, DNS resolvers!</p><p>1.1.1.1 protects you from all of that for a price of $0 (yes, you read that right). A <strong>FREE</strong>, fast, secure DNS server that respects your privacy — sounds too good to be true, doesn’t it? But it’s an effort towards building a better Internet. You don’t have to take my words for that, read more about it on <a href="https://blog.cloudflare.com/announcing-1111/">Cloudflare’s</a> and <a href="https://labs.apnic.net/?p=1127">APNIC’s</a> blogs.</p><h3>Switching DNS nameservers via bash</h3><p>As I mentioned previously, it’s too much of a hassle for me to go manually update my DNS configurations via Network Settings UI whenever I jump back and forth between school’s and home’s networks. Naturally, I would try to figure out how to do it via my terminal. That’s when I found out about this awesome tool that comes in-built with macOS — networksetup. With networksetup, changing my DNS config is as simple as</p><pre># Adding 1.1.1.1 to my nameservers<br>networksetup -setdnsservers Wi-Fi 1.1.1.1<br># Clear all my alternate nameservers<br>networksetup -setdnsservers Wi-Fi empty</pre><p>Now I can easily configure my DNS resolvers to be the same as the instructions for 1.1.1.1 by</p><pre># 4 nameservers - 2 IPv4 &amp; 2 IPv6<br>networksetup -setdnsservers Wi-Fi 1.1.1.1 1.0.0.1 2606:4700:4700::1111 2606:4700:4700::1001</pre><p>But this is quite lengthy to type as well, even though I would prefer this over Network Preferences UI any day. So I went another level of laziness by wrapping it inside a bash function</p><pre>function dns1111() {<br>    networksetup -setdnsservers Wi-Fi 1.1.1.1 1.0.0.1 2606:4700:4700::1111 2606:4700:4700::1001<br>}</pre><p>This seems to be pretty neat and does the job, but hold on a second. How about switching it back to empty? A flag/argument to this bash function would do the job fine but I want to use the least effort as possible and save myself the mental burden of remembering whether my alternate DNS config is empty. So I thought to myself: “Why not detect the current state and switch the DNS config based on that? Like a literal switch button — you <strong>don’t</strong> have to tell what state you want it to be, just the opposite of what comes before”. With that in mind, I found this option for networksetup that would tell me the current state</p><pre>networksetup -getdnsservers Wi-Fi</pre><p>It would list all of my alternate DNS servers, or print out this string There aren&#39;t any DNS Servers set on Wi-Fi if there isn’t any. And now I can complete my function, with some cool emojis to signify whether the config has just been applied or removed</p><pre>function dns1111() {<br>  if [[ $(networksetup -getdnsservers Wi-Fi) == &quot;There aren&#39;t any DNS Servers set on Wi-Fi&quot;* ]]; then<br>    networksetup -setdnsservers Wi-Fi 1.1.1.1 1.0.0.1 2606:4700:4700::1111 2606:4700:4700::1001 &amp;&amp; echo 🚀 🚀 🚀<br>  else<br>    networksetup -setdnsservers Wi-Fi empty &amp;&amp; echo 🚦 🚦 🚦<br>  fi<br>}</pre><p>Now I can easily switch back and forth between 1.1.1.1 and default DNS configs with just a short command dns1111</p><h3>Bonus: Linux based machine</h3><p>Since networksetup is not available on Linux based OSes, we would need to use a different package that helps handle DNS nameservers called dnsmasq.</p><p>Install it using your distro’s package manager</p><pre># this is for Ubuntu<br>sudo apt-get install dnsmasq</pre><p>Installing the package will come with a config file /etc/dnsmasq.conf for us to update the nameservers. I have included my implementation below. If you have a better approach, don’t hesitate to reach out to me at <a href="/service/mailto:hung.ngn.the@gmail.com">hung.ngn.the@gmail.com</a></p><pre>function dns1111() {<br>  # detect existence of the backup file to see whether the config is in place<br>  if [ -e /etc/dnsmasq.conf.bak ]]; then<br>    sudo mv /etc/dnsmasq.conf.bak /etc/dnsmasq.conf &amp;&amp; echo 🚦 🚦 🚦<br>  else<br>    sudo cp /etc/dnsmasq.conf /etc/dnsmasq.conf.bak &amp;&amp; \<br>    sudo echo &quot;server=1.1.1.1<br>server=1.0.0.1<br>server=2606:4700:4700::1111<br>server=2606:4700:4700::1001&quot; &gt;&gt; /etc/dnsmasq.conf &amp;&amp; \<br>    sudo service dnsmasq restart &amp;&amp; \<br>    sudo service network-manager restart &amp;&amp; \<br>    echo 🚀 🚀 🚀<br>  fi<br>}</pre><p>This article was originally published on <a href="https://thecodecousins.com/posts/dns-command/">thecodecousins</a>.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fupscri.be%2Fc36271%3Fas_embed%3Dtrue&amp;dntp=1&amp;display_name=Upscribe&amp;url=https%3A%2F%2Fupscri.be%2Fc36271&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=upscri" width="800" height="400" frameborder="0" scrolling="no"><a href="https://medium.com/media/1f83e4a733ad3206f47e6dd38aa4fc6d/href">https://medium.com/media/1f83e4a733ad3206f47e6dd38aa4fc6d/href</a></iframe><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4ffa407dfd38" width="1" height="1" alt=""><hr><p><a href="https://medium.easyread.co/quick-terminal-based-dns-switching-4ffa407dfd38">Quick terminal-based DNS switching</a> was originally published in <a href="https://medium.easyread.co/">Easyread</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>