<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Jeremy Ong</title>
    <description>The latest articles on Forem by Jeremy Ong (@jeremyong).</description>
    <link>https://forem.com/jeremyong</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F277850%2F092e665a-ec58-4fbe-85a6-2c4ddf9e6520.png</url>
      <title>Forem: Jeremy Ong</title>
      <link>https://forem.com/jeremyong</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/jeremyong"/>
    <language>en</language>
    <item>
      <title>C++ Neural Network in a Weekend</title>
      <dc:creator>Jeremy Ong</dc:creator>
      <pubDate>Tue, 29 Sep 2020 14:18:21 +0000</pubDate>
      <link>https://forem.com/jeremyong/c-neural-network-in-a-weekend-46oa</link>
      <guid>https://forem.com/jeremyong/c-neural-network-in-a-weekend-46oa</guid>
      <description>&lt;p&gt;There are tons of ML/AI frameworks to choose from right now (Tensorflow, Keras, Pytorch, etc). However, sometimes, building things completely from scratch is still the best way to learn how something works. To help people seeking to to this, I created a neural network in a weekend in C++ &lt;em&gt;without any external dependencies&lt;/em&gt;. No matrix libraries, no linear algebra subroutines, nada, zilch.&lt;/p&gt;

&lt;p&gt;The code for the repository is &lt;a href="https://github.com/jeremyong/cpp_nn_in_a_weekend"&gt;here&lt;/a&gt;. I also wrote a paper tutorial walking through the math (the derivations) and the implementation hosted in the same repository &lt;a href="https://github.com/jeremyong/cpp_nn_in_a_weekend/blob/master/doc/DOC.pdf"&gt;here&lt;/a&gt;. The paper is... a bit on the long side at 42 pages, but it's meant to be entirely self-contained. Again, from scratch!&lt;/p&gt;

&lt;p&gt;In addition, the code is pretty heavily annotated with comments, so should hopefully be easy to following along, but please leave a question/comment if you get stuck. Happy hacking!&lt;/p&gt;

</description>
      <category>cpp</category>
      <category>machinelearning</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Recommendations for Aspiring Graphics Engineers</title>
      <dc:creator>Jeremy Ong</dc:creator>
      <pubDate>Sun, 27 Sep 2020 20:28:33 +0000</pubDate>
      <link>https://forem.com/jeremyong/recommendations-for-aspiring-graphics-engineers-42m3</link>
      <guid>https://forem.com/jeremyong/recommendations-for-aspiring-graphics-engineers-42m3</guid>
      <description>&lt;p&gt;At the start of 2020, I wrote &lt;a href="https://twitter.com/m_ninepoints/status/1215429886715629569"&gt;this twitter thread&lt;/a&gt; on recommendations for engineers that are self-studying to get into graphics engineering. I decided to extract the contents of that thread here, with additional exposition for the &lt;a href="https://dev.to"&gt;dev.to&lt;/a&gt; community.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a graphics/rendering engineer?
&lt;/h2&gt;

&lt;p&gt;Most of my followers on twitter are already graphics practitioners or on their way to becoming one, but the demographic here isn't skewed in such a way, so it makes sense to describe graphics engineering a bit more.&lt;/p&gt;

&lt;p&gt;Graphics engineers are typically found in the games and film industry, although you'll find them in all sorts of nooks within the industry (automotive, CAD software, research labs, etc). If I had to summarize a graphics programmer's job, it's to take a &lt;em&gt;scene description&lt;/em&gt; and produce a &lt;em&gt;render&lt;/em&gt;. By a "scene description," what I mean is a (potentially huge) data structure describing all the geometry, skeletons, particles, terrain, atmospheric properties, in the environment &lt;em&gt;in addition to&lt;/em&gt; all the "material data" which describes how all the aforementioned objects respond to light. Furthermore, the scene description will contain a bunch of lighting information (how many lights where, which ones cast shadows, what is the range of each light's influence, what does the skybox look like, etc). These two chunks of data (materials in the scene, and lights in the scene) dictate what is "seen" by a virtual camera used to render the scene to a monitor, image, VR goggles, or what have you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IwYzI2z_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ry671idzdzt8b42hssi3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IwYzI2z_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ry671idzdzt8b42hssi3.png" alt='A "scene"' width="880" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To produce an image like the above, a ton of things are happening. First, every object pose (aka, position + orientation) must be transposed to the same "view space" of the camera. This information tells us where in the frame the object should be, and how far away from the camera it is. This same transformation needs to occur for every light in the scene as well. The crucial piece of information produced by these transforms is "occlusion" information. When an object &lt;code&gt;A&lt;/code&gt; is between a point and another object &lt;code&gt;B&lt;/code&gt;, we say that &lt;code&gt;B&lt;/code&gt; &lt;em&gt;is occluded by&lt;/em&gt; &lt;code&gt;A&lt;/code&gt;. If the occluder is occluding light, the object behind the occluder is in shadow. If the occluder is between the object and the camera, the object occluded will not be directly visible in full.&lt;/p&gt;

&lt;p&gt;After the geometrical information is processed (either via ray tracing, rasterization, or some other technique), the next step is &lt;em&gt;lighting&lt;/em&gt;. Lighting is a huge topic, and it would be impossible to cover every aspect of lighting. In short however, every graphics engineer is trying to solve or approximate the equation below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rwxsYjD7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5vdenudb239eqvo2610o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rwxsYjD7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5vdenudb239eqvo2610o.png" alt="The Rendering Equation" width="725" height="739"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That integral above needs to be solved for &lt;em&gt;every pixel&lt;/em&gt; in the frame. What makes matters worse is that because light "bounces" off surfaces (not just mirrors, any material that isn't vantablack or a black hole will reflect light to some degree) or "refracts" through transmissive surfaces (e.g. light bending through water), the equation above is a &lt;em&gt;recursive&lt;/em&gt; one. Solving for all inbound light impinging on a surface requires that exitant light from nearby surfaces must be solved for as well. This includes surfaces occluded from the camera! This summarizes what is known as the &lt;em&gt;global illumination&lt;/em&gt; problem.&lt;/p&gt;

&lt;p&gt;Finally, many graphics engineers are also concerned with the performance of their renderer. Rendering frames accurately requires a lot of computation, so rendering typically occurs on an &lt;em&gt;accelerator&lt;/em&gt; (most commonly, the GPU). On top of having SIMD/SIMT architectures exhibiting hierarchical parallelism, GPUs have dedicated hardware for quickly performing various operations (rasterization, texture sampling, screen-space fragment derivatives, vertex attribute interpolation, etc). Using a GPU effectively also requires a sound understanding of properly utilizing a CPU. The CPU needs to "stay ahead" of the GPU to ensure the GPU always has work to do. As a result, many renderers are heavily multi-threaded and pipelined. Film rendering engineers are typically more concerned with accuracy, while game rendering engineers are typically more concerned with latency. Either way, the rendering problem is sufficiently complicated and computationally heavy that performant code is always the objective.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why would I want to be a graphics engineer?
&lt;/h2&gt;

&lt;p&gt;This is a fair question! You'll get a different answer from every graphics engineer, but my favorite aspects are the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Highly interdisciplinary. Graphics engineers draw wisdom and knowledge from the following fields

&lt;ul&gt;
&lt;li&gt;Art&lt;/li&gt;
&lt;li&gt;Photography&lt;/li&gt;
&lt;li&gt;Math (Linear Algebra, Geometry, Analysis, and more)&lt;/li&gt;
&lt;li&gt;Computer Science&lt;/li&gt;
&lt;li&gt;Electrical Engineering (in particular, signals processing)&lt;/li&gt;
&lt;li&gt;Physics&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Finding solutions to problems in graphics typically requires some degree of creativity and sophistication&lt;/li&gt;
&lt;li&gt;What you ultimately produce is usually pretty awesome&lt;/li&gt;
&lt;li&gt;Colleagues in the field of graphics are generally passionate about their discipline, and exceedingly fun to talk with&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In addition to the above, because graphics engineers are usually hard to find, they tend to be in high demand as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are important topics a graphics engineer should study?
&lt;/h2&gt;

&lt;p&gt;I find I draw from the following subjects the most consistently:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Geometry (and Geometric Algebra)

&lt;ul&gt;
&lt;li&gt;Trigonometry&lt;/li&gt;
&lt;li&gt;Quaternions/Dual-quaternions&lt;/li&gt;
&lt;li&gt;Linear transformations&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Analysis

&lt;ul&gt;
&lt;li&gt;Calculus/vector-calculus&lt;/li&gt;
&lt;li&gt;Approximation theory&lt;/li&gt;
&lt;li&gt;Statistics (Monte-Carlo, moment analysis, etc.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Linear Algebra

&lt;ul&gt;
&lt;li&gt;Vector spaces&lt;/li&gt;
&lt;li&gt;Orthogonalization&lt;/li&gt;
&lt;li&gt;Various decompositions (SVD, QR)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Computer Science

&lt;ul&gt;
&lt;li&gt;Memory hierarchy (CPU and GPU cache architecture)&lt;/li&gt;
&lt;li&gt;Algorithms and data structures&lt;/li&gt;
&lt;li&gt;Compression/codecs&lt;/li&gt;
&lt;li&gt;Programming language that's "close to the metal" (C, C++, Rust, etc)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Specialized knowledge on tools and APIs

&lt;ul&gt;
&lt;li&gt;Graphics APIs depending on your target platform (for me, that's D3D12, Vulkan, PS5, Nintendo Switch)&lt;/li&gt;
&lt;li&gt;Profilers from Nvidia/AMD&lt;/li&gt;
&lt;li&gt;Crash reporting tools (e.g. Nvidia's excellent &lt;a href="https://developer.nvidia.com/nsight-aftermath"&gt;Aftermath&lt;/a&gt; tool)&lt;/li&gt;
&lt;li&gt;Frame capture tools such as the amazing &lt;a href="https://renderdoc.org/"&gt;RenderDoc&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;(Some degree of) Knowledge of how Art tools work

&lt;ul&gt;
&lt;li&gt;Depending on the specialization within graphics you choose, it can be helpful to understand how artists are producing the data you will ultimately use (and in some cases, you're the one that builds the tools). Such tools include:&lt;/li&gt;
&lt;li&gt;3D Modeling software (Maya, 3DS Max, ZBrush, etc)&lt;/li&gt;
&lt;li&gt;Material editors (Substance, various game engines, Nvidia's MDL)&lt;/li&gt;
&lt;li&gt;Scene compositors (Blender, game engines, various data formats such as GLTF, Universal Scene Description, etc)&lt;/li&gt;
&lt;li&gt;Heightmap editors&lt;/li&gt;
&lt;li&gt;Animation/motion-capture (mocap) tools&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;YOU DO NOT NEED TO KNOW ALL OF THE ABOVE TO GET STARTED&lt;/strong&gt;. I can't emphasize this enough. As you can see, graphics engineers will typically get exposed to reams of tools, technologies, techniques, and concepts over the course of their careers. That's part of the fun!&lt;/p&gt;

&lt;h2&gt;
  
  
  OK, fine, but how do I get started?
&lt;/h2&gt;

&lt;p&gt;There are two general approaches to learning anything, top-down, and bottom-up. I'll address these two approaches separately, with the caveat that most people will do a combination of both simultaneously. The degree that you go top-down vs bottom-up will depend on your learning style. Personally, as an example, I tend to go as bottom-up as possible, switching over to top-down only when I get stuck or if a concept gets lost on me.&lt;/p&gt;

&lt;h3&gt;
  
  
  Top-down
&lt;/h3&gt;

&lt;p&gt;When learning top-down, the idea is to get the broad strokes of the concept and understand what's possible, without necessarily grasping all the details about implementation as you go. Taking this route, you will generally start with an off-the-shelf engine, such as &lt;a href="https://www.unrealengine.com/"&gt;Unreal Engine&lt;/a&gt;, &lt;a href="https://godotengine.org/"&gt;Godot&lt;/a&gt;, or &lt;a href="https://unity.com/"&gt;Unity&lt;/a&gt;. For learning purposes, I actually suggest engines that provide source code (i.e. not Unity) because while learning with a closed source engine is possible, it's a strict disadvantage compared to learning with source available. If C++ or C# aren't your cup of tea, there are plenty of other engines to get started in your programming language of choice. Be advised, however, that most graphics engineers in the wild are C++ programmers.&lt;/p&gt;

&lt;p&gt;After picking an engine, you should try to accomplish the following tasks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Draw an object to the screen&lt;/li&gt;
&lt;li&gt;Assign a material to the object to change its appearance&lt;/li&gt;
&lt;li&gt;Change various draw parameters to affect the object's "draw mode" (e.g. transparency, emissive)&lt;/li&gt;
&lt;li&gt;Make a custom material to color the object something simple with a custom shader&lt;/li&gt;
&lt;li&gt;Implement a custom material that responds to a directional light (e.g. &lt;a href="https://en.wikipedia.org/wiki/Phong_shading"&gt;Phong Shading&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Implement a custom material that samples from a texture&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After doing the above, you should at least have a sense for how more complicated lighting models might work, without necessarily understanding the underlying math and techniques associated with it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bottom Up
&lt;/h3&gt;

&lt;p&gt;If you take the bottom up approach, this means you want to control everything from the data, to how the data is fed to the GPU as draw calls, to what the GPU does with the draw calls, to how the draws are ultimately composited to a screen or offscreen raster (e.g. image or PDF).&lt;/p&gt;

&lt;p&gt;To go this route, I recommend starting with either D3D11 or OpenGL (or WebGL on the web, be sure not to use Safari which has a crippled WebGL implementation). There are more modern APIs, namely D3D12 and Vulkan, but these APIs are notoriously difficult to use and designed for professionals looking to eke out as much performance as possible. The higher-level APIs such as D3D11 and OpenGL provide abstractions over memory barriers, resource ref counting, and generally take care of any potential data hazards that would otherwise crash your program. To summarize the problem, imagine you allocate memory on the GPU for a texture, and then submit a draw call that uses that texture. Later, you realize you need that memory for something else. In D3D12 and Vulkan, you must ensure that the draw call that reads from that memory has finished executing on the GPU before modifying that memory. There are many ways to do this, but in D3D11/OpenGL, such concerns are off the table because the API handles the memory management for you behind the scenes.&lt;/p&gt;

&lt;p&gt;A great tutorial for OpenGL in particular is the &lt;a href="https://learnopengl.com/"&gt;Learn OpenGL&lt;/a&gt; tutorial, which covers the entire construction of a basic renderer. When I say "basic" here, I don't mean that all the techniques are basic, just that the architecture itself is relatively basic. If you follow it end to end, you'll learn a lot of techniques such as SSAO, bloom, and more. The still below, for example, shows the image you can create with specular image based lighting (Spec IBL).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rwAtyHi4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/g83we1mpvitx7t31tnth.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rwAtyHi4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/g83we1mpvitx7t31tnth.png" alt="Alt Text" width="600" height="543"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Moving past the beginner stage
&lt;/h2&gt;

&lt;p&gt;You've seen homogeneous coordinates, you've seen linear algebra, perspective transforms, shadow mapping, and a number of techniques. Hopefully, at this point, you've also taken some interviews. I'm a big believer that graphics engineers will learn &lt;em&gt;most&lt;/em&gt; of that they know on the job, so do interview early and often to try to get a foot in the door. Generally, aptitude is more heavily weighed over experience in a graphics engineering interview, especially for a more entry level position.&lt;/p&gt;

&lt;p&gt;From there, how do you progress to be a senior or lead graphics engineer? Here's a list of some tips I've compiled over the years.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start avoiding commercial engines and frameworks in your own experimentation. This gets you away from the "magic" of how things happen and forces you to learn it from the ground up.&lt;/li&gt;
&lt;li&gt;Expect to have 3 projects just generally floating in the background at once. A prototyping engine for trying out new ideas, an unbiased path-tracing engine (follow &lt;a href="http://www.pbr-book.org/"&gt;the PBR book&lt;/a&gt; to get started here) useful for validating results, and a "production engine" where you try to push perf to the limits.&lt;/li&gt;
&lt;li&gt;Continue studying linear algebra, geometric algebra, calculus, fourier/harmonic analysis, and any other math topics as they arise in your work&lt;/li&gt;
&lt;li&gt;Pay attention to data structures, particularly acceleration structures but also understand the importance of proper I-cache/D-cache utilization, branching effects, and have a good feeling for the latencies involved&lt;/li&gt;
&lt;li&gt;From time to time, pick a paper from ACM, Arxiv, etc that interests you and try to implement it from start to finish&lt;/li&gt;
&lt;li&gt;If you can, publish!&lt;/li&gt;
&lt;li&gt;Learn about compression and aliasing. Maybe write a BCn codec or a DCT or wavelet codec. Experiment with the various forms of AA and analyze their tradeoffs (MSAA, MLAA, FXAA, TXAA, etc).&lt;/li&gt;
&lt;li&gt;Stay up to date on the tools available and what they are capable of (shader compilers, shader assembly analyzers, frame dumps, GPU crash analyzers, mesh optimizers, texture compressors, etc).&lt;/li&gt;
&lt;li&gt;Shadow the artists you work with to understand their workflows and generate ideas on how to make things better.&lt;/li&gt;
&lt;li&gt;Study other engines, especially from studios like idTech, Ubisoft, Blizzard, Rockstar, etc (sorry if I didn't mention your studio!). The point is to study engines that achieve something you're looking to achieve as well.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Of paramount importance is to recognize that like other highly specialized disciplines, graphics engineering isn't something you can learn in a fortnight. In fact, it's likely something you'll never stop learning about. Staying humble throughout your career, never being able to admit when you're wrong, and constantly seeking to improve are surefire ways to advance and find fulfillment while doing so.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;This post was a bit difficult to write because I had to tread the line between offering too much (which gets overwhelming fast), or offering too little, in which case a concept/term is lost on the reader. Also, there's a thin line between showing innumerable possibilities that inspire, vs showing an impossibly high mountain that crushes spirits. To all who venture down this path, welcome! It's a challenging road to be sure, but you're in very good company. Never forget that if something is challenging for you, chances are, it's challenging for others as well. Personally, I've never encountered a graphics engineer that wasn't welcoming towards questions and showing a fellow engineer the ropes. On that note, any interested would-be graphics engineers are welcome to comment with questions! I often field questions sent as DMs on twitter as well, so you're more than welcome to contact me there.&lt;/p&gt;

</description>
      <category>rendering</category>
      <category>graphics</category>
      <category>cpp</category>
      <category>career</category>
    </item>
    <item>
      <title>More Eyes, Plz! Hackathon entry + update</title>
      <dc:creator>Jeremy Ong</dc:creator>
      <pubDate>Sat, 22 Aug 2020 19:02:53 +0000</pubDate>
      <link>https://forem.com/jeremyong/more-eyes-plz-hackathon-entry-update-3825</link>
      <guid>https://forem.com/jeremyong/more-eyes-plz-hackathon-entry-update-3825</guid>
      <description>&lt;p&gt;&lt;em&gt;Here are the updates made since the original progress post:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;added support for bot commands, starting with the &lt;code&gt;[meep close]&lt;/code&gt; command&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;the site now fetches labels created dynamically for new meep requests&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;the site is now responsive on mobile/tablet&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;meeps are only filed at most once (in the event the same commit is pushed to multiple branches)&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Thanks to everyone who took it for a spin and/or submitted feedback!&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  My Workflow
&lt;/h3&gt;

&lt;p&gt;Getting started with a new programming language or framework? Trying to learn a new paradigm like functional programming? Perhaps you're getting started with a new discipline, like compiler engineering or graphics programming? Normally, we'd learn by authoring code in a "sandbox" and see what works while following along a book or tutorial. But what if you would like some pointers or feedback from someone more experienced? Stackoverflow could work, but the site encourages questions with a high degree of specificity. You can't just ask for holistic feedback for code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://moreeyesplz.com" rel="noopener noreferrer"&gt;More Eyes, Plz!&lt;/a&gt; is a service powered by Github actions to easily crowdsource commits made to any repository. To get started simply install the &lt;a href="https://github.com/marketplace/actions/meep-scanner" rel="noopener noreferrer"&gt;meep scanner&lt;/a&gt; action to the repo you'd like to get feedback on, or copy the following snippet to &lt;code&gt;.github/workflows/meep.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;meep_scanner&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;meep_scan&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;moreeyesplz/meep_scanner@master&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;github-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;After the action is installed, simply write &lt;code&gt;[meep]&lt;/code&gt; or &lt;code&gt;[MEEP]&lt;/code&gt; somewhere in the commit notes. Here's an example commit message:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fert3831mltx9m7tvzrco.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fert3831mltx9m7tvzrco.png" alt="commitmessage"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Shortly after, the bot will post a comment that looks like the following to the commit itself:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fj8734jlj7zgcorhwd6sv.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fj8734jlj7zgcorhwd6sv.PNG" alt="commitcomment"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The commit link will also be indexed on the &lt;a href="https://moreeyesplz.com" rel="noopener noreferrer"&gt;website&lt;/a&gt; and show up as follows for others to see:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fu7pd5cm8hmrq4nr27643.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fu7pd5cm8hmrq4nr27643.png" alt="meep"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Afterwards, expect to get some feedback!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ffysw1je00zdvllecyitg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ffysw1je00zdvllecyitg.png" alt="feedback"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you're satisfied with the feedback, simply write &lt;code&gt;[meep close]&lt;/code&gt; in the commit thread to unlist the meep from the database.&lt;/p&gt;
&lt;h3&gt;
  
  
  Submission Category:
&lt;/h3&gt;

&lt;p&gt;Maintainer Must Haves&lt;/p&gt;
&lt;h3&gt;
  
  
  Yaml File or Link to Code
&lt;/h3&gt;

&lt;p&gt;Only one repository is relevant for actually integrating with the &lt;strong&gt;More Eyes, Plz!&lt;/strong&gt; service: &lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/moreeyesplz" rel="noopener noreferrer"&gt;
        moreeyesplz
      &lt;/a&gt; / &lt;a href="https://github.com/moreeyesplz/meep_scanner" rel="noopener noreferrer"&gt;
        meep_scanner
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Integrate this action into your repository to start crowdsourcing feedback for your commits!
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;MEEP Scanner&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;The &lt;em&gt;MEEP Scanner&lt;/em&gt; is a lightweight action that integrates with the &lt;a href="https://moreeyesplz.com" rel="nofollow noopener noreferrer"&gt;More Eyes, Plz!&lt;/a&gt; service. When installed in a repository, the scanner scans commit messages for the &lt;code&gt;[MEEP]&lt;/code&gt; or &lt;code&gt;[meep]&lt;/code&gt; string. When present, the commit becomes indexed in the MEEP database for others in the community to discover.&lt;/p&gt;

&lt;p&gt;Note that this action only works on repositories that are &lt;em&gt;public&lt;/em&gt; at this time. Without public visibility, outside members of the community will not be able to view your commits to provide feedback.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Usage&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;For each repository you wish to potentially request feedback on, please performing the following steps:&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;1. Add topics to your repository&lt;/h3&gt;
&lt;/div&gt;

&lt;p&gt;Topics added help others discover your requests more easily through filters. To do this, hit the settings wheel at the top right of your repository's home page. Then, add topics that pertain to your code. This could be the programming language, a framework or library…&lt;/p&gt;
&lt;/div&gt;


&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/moreeyesplz/meep_scanner" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;The action that responds to the scan to write the commit comment and also file an issue to the meeps tracker is here:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/moreeyesplz" rel="noopener noreferrer"&gt;
        moreeyesplz
      &lt;/a&gt; / &lt;a href="https://github.com/moreeyesplz/meeper" rel="noopener noreferrer"&gt;
        meeper
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Internal action which accepts meeps scanned by the meep_scanner
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;meeper&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;Internal action which accepts meeps scanned by the meep_scanner.
This action is dispatched only via an API call to invoke the &lt;code&gt;workflow_dispatch&lt;/code&gt; event.&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/moreeyesplz/meeper" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;Another action is used to respond to &lt;code&gt;workflow_dispatch&lt;/code&gt; events needed to power bot commands such as &lt;code&gt;[meep close]&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/moreeyesplz" rel="noopener noreferrer"&gt;
        moreeyesplz
      &lt;/a&gt; / &lt;a href="https://github.com/moreeyesplz/themeepbot" rel="noopener noreferrer"&gt;
        themeepbot
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Webhook responder for themeepbot
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;themeepbot&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;Webhook responder for themeepbot&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/moreeyesplz/themeepbot" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;Finally, the website itself is hosted via github pages and is itself open source as well:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/moreeyesplz" rel="noopener noreferrer"&gt;
        moreeyesplz
      &lt;/a&gt; / &lt;a href="https://github.com/moreeyesplz/moreeyesplz.github.io" rel="noopener noreferrer"&gt;
        moreeyesplz.github.io
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Public-facing website aggregating and displaying meeps
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;br&gt;
&lt;p&gt;
  &lt;a href="https://github.com/moreeyesplz" rel="noopener noreferrer"&gt;
    &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fmoreeyesplz%2Fmoreeyesplz.github.iopublic%2Feye-apple-touch-icon.png" alt="Logo" width="80" height="80"&gt;
  &lt;/a&gt;
  &lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;More Eyes, Plz!&lt;/h1&gt;
&lt;/div&gt;
  &lt;p&gt;
    A simple way to crowdsource feedback on your GitHub commits
    &lt;br&gt;
    &lt;br&gt;
    &lt;a href="http://moreeyesplz.com/" rel="nofollow noopener noreferrer"&gt;Go To Site&lt;/a&gt;
    ·
    &lt;a href="https://github.com/marketplace/actions/meep-scanner" rel="noopener noreferrer"&gt;Get MEEP Scanner&lt;/a&gt;
    ·
    &lt;br&gt;
    &lt;a href="https://github.com/moreeyesplz/moreeyesplz.github.io#traffic_light-roadmap" rel="noopener noreferrer"&gt;Report Bug&lt;/a&gt;
    ·
    &lt;a href="https://github.com/moreeyesplz/moreeyesplz.github.io#traffic_light-roadmap" rel="noopener noreferrer"&gt;Request Feature&lt;/a&gt;
  &lt;/p&gt;


&lt;br&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Table of Contents&lt;/h2&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;✨ &lt;a href="https://github.com/moreeyesplz/moreeyesplz.github.io#sparkles-about-the-project" rel="noopener noreferrer"&gt;About the Project&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;🧩 &lt;a href="https://github.com/moreeyesplz/moreeyesplz.github.io#jigsaw-how-does-it-all-work" rel="noopener noreferrer"&gt;How does it all work?&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🔧 &lt;a href="https://github.com/moreeyesplz/moreeyesplz.github.io#wrench-built-with" rel="noopener noreferrer"&gt;Built With&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;🚀 &lt;a href="https://github.com/moreeyesplz/moreeyesplz.github.io#rocket-getting-started" rel="noopener noreferrer"&gt;Getting Started&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;🌊 &lt;a href="https://github.com/moreeyesplz/moreeyesplz.github.io#ocean-workflow" rel="noopener noreferrer"&gt;Workflow&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;🚥 &lt;a href="https://github.com/moreeyesplz/moreeyesplz.github.io#traffic_light-roadmap" rel="noopener noreferrer"&gt;Roadmap&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🤝 &lt;a href="https://github.com/moreeyesplz/moreeyesplz.github.io#handshake-contributing" rel="noopener noreferrer"&gt;Contributing&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;☎️ &lt;a href="https://github.com/moreeyesplz/moreeyesplz.github.io#phone-contact" rel="noopener noreferrer"&gt;Contact&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📚 &lt;a href="https://github.com/moreeyesplz/moreeyesplz.github.io#books-resources" rel="noopener noreferrer"&gt;Resources&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;br&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;✨ About The Project&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;This is a weekend project built by &lt;a href="https://github.com/jeremyong" rel="noopener noreferrer"&gt;Jeremy&lt;/a&gt; and &lt;a href="https://github.com/duchess-toffee" rel="noopener noreferrer"&gt;Hannah Ong&lt;/a&gt; as a submission to the &lt;a href="https://dev.to/devteam/announcing-the-github-actions-hackathon-on-dev-3ljn" rel="nofollow"&gt;Dev.to's GitHub Actions Hackathon&lt;/a&gt;. We thought that in the spirit of Dev.to and GitHub's community, that a community-led resource to help people like myself get feedback on GitHub commits would be a great submission. It went on to win the grand prize in the &lt;a href="https://dev.to/devteam/github-actions-hackathon-winners-announced-38o2" rel="nofollow"&gt;"Maintainer Must-Haves"&lt;/a&gt; category.&lt;/p&gt;
&lt;p&gt;This &lt;code&gt;README&lt;/code&gt; in particular will be the main documentation that explains how all the repos within &lt;a href="https://github.com/moreeyesplz" rel="noopener noreferrer"&gt;More Eyes, Plz&lt;/a&gt; are connected.&lt;/p&gt;
&lt;br&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;🧩 How Does It All Work?&lt;/h3&gt;…&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/moreeyesplz/moreeyesplz.github.io" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Even the deployment of the site (created via &lt;code&gt;create-react-app&lt;/code&gt;) is automated using a workflow which may be useful to others. The code used is pasted below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;workflow_dispatch'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;checkout&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;master&lt;/span&gt;
        &lt;span class="na"&gt;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
        &lt;span class="na"&gt;clean&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cache dependencies&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cache&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/cache@v2&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node_modules&lt;/span&gt;
        &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ runner.os }}-${{ hashFiles('package.json') }}&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;install dependencies&lt;/span&gt;
      &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;steps.cache.outputs.cache-hit != 'true'&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm install&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build site&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;npm run build&lt;/span&gt;
        &lt;span class="s"&gt;git config --local user.email "bot@moreeyesplz.com"&lt;/span&gt;
        &lt;span class="s"&gt;git config --local user.name "themeepbot"&lt;/span&gt;
        &lt;span class="s"&gt;git checkout site&lt;/span&gt;
        &lt;span class="s"&gt;cp -r build/* ./&lt;/span&gt;
        &lt;span class="s"&gt;rm -rf build&lt;/span&gt;
        &lt;span class="s"&gt;git add .&lt;/span&gt;
        &lt;span class="s"&gt;git commit -m'Automated build'&lt;/span&gt;
        &lt;span class="s"&gt;git push origin site&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Feel free to adapt this for your needs! The only thing you would probably want to change is the identifying &lt;code&gt;user.email&lt;/code&gt; and &lt;code&gt;user.name&lt;/code&gt; local git config (it needs to push to the &lt;code&gt;site&lt;/code&gt; branch).&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional Resources / Info
&lt;/h3&gt;

&lt;p&gt;This project was done in collaboration with &lt;a class="mentioned-user" href="https://dev.to/duchesstoffee"&gt;@duchesstoffee&lt;/a&gt; (my wife). She did the frontend, responsible for querying the meeps database (hosted using the issue tracker of this &lt;a href="https://github.com/moreeyesplz/meeps" rel="noopener noreferrer"&gt;meta repo&lt;/a&gt;), fetch labels, and enabling tag-based searching. The frontend is built as a single page application, using the &lt;code&gt;fetch&lt;/code&gt; API and OAuth credentials to pull data from Github.&lt;/p&gt;

&lt;p&gt;Here is a list of dependencies used for the frontend:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://reactjs.org/" rel="noopener noreferrer"&gt;React&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://material-ui.com/" rel="noopener noreferrer"&gt;Material UI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/octokit/rest.js" rel="noopener noreferrer"&gt;Octokit Rest&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Github API v3 REST client&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;The backend is largely driven via actions and Github itself, with no database or dedicated servers. However, a few Google Cloud functions are used simply to proxy information in a secure manner in cases where we could not ship a developer secret (with write credentials to the meeps repo) to the client. A Google Cloud function is also used to handle the oauth redirect. Here's the code for the Oauth handling for example, which runs on vanilla nodejs with no dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;https&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;HOST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HOST&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;github.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/login/oauth/access_token?code=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;client_id=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OAUTH_ID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;client_secret=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OAUTH_SECRET&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;Accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;login_res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;login_res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setEncoding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

        &lt;span class="nx"&gt;login_res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;login_res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nx"&gt;login_res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;end&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;HOST&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?login_failed`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;HOST&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?access_token=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;scope=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the code for the Google Cloud function which handles the Github Webhook and forwards it as a workflow dispatch event to &lt;a href="https://github.com/moreeyesplz/themeepbot/blob/master/.github/workflows/bot.yml" rel="noopener noreferrer"&gt;themeepbot bot workflow&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;https&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webhook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Validate github user-agent&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user-agent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GitHub-Hookshot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-github-event&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Missing event from request payload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;master&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;api.github.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/repos/moreeyesplz/themeepbot/actions/workflows/bot.yml/dispatches`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;Accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/vnd.github.v3+json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`token &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;User-Agent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;moreeyesplz&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Length&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setEncoding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
        &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;end&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;204&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Workflow not dispatched: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main "implementation detail" here is that workflow event payloads have far more keys and nested objects than what the workflow_dispatch can receive as inputs (limit is 10). As a workaround, this event proxy simply base64 encodes the entire payload and forwards it as a single event for the downstream action to parse later. Thus, the "heavy lifting" that needs to interact with the Github APIs can be done in action-based workflows.&lt;/p&gt;

&lt;p&gt;All the actions and workflow code are very light on dependencies, using only the actions toolkit.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/actions/toolkit/tree/master/packages/core" rel="noopener noreferrer"&gt;actions/core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/actions/toolkit/tree/master/packages/github" rel="noopener noreferrer"&gt;actions/github&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>actionshackathon</category>
      <category>help</category>
    </item>
    <item>
      <title>Introducing MEEP: More Eyes, Plz! Testers wanted!</title>
      <dc:creator>Jeremy Ong</dc:creator>
      <pubDate>Tue, 18 Aug 2020 04:20:02 +0000</pubDate>
      <link>https://forem.com/jeremyong/introducing-meep-more-eyes-plz-testers-wanted-3h67</link>
      <guid>https://forem.com/jeremyong/introducing-meep-more-eyes-plz-testers-wanted-3h67</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Are you starting your coding journey? Experienced but venturing into unfamiliar territory? My &lt;a href="https://dev.to/duchesstoffee"&gt;wife&lt;/a&gt; and I are pleased to show the first version of &lt;a href="https://moreeyesplz.com"&gt;&lt;em&gt;More Eyes, Plz!&lt;/em&gt;&lt;/a&gt;, a new service powered by &lt;a href="https://github.com/features/actions"&gt;Github Actions&lt;/a&gt; to crowdsource feedback, suggestions, and insight on your commits.&lt;/p&gt;

&lt;p&gt;This is a WIP but we believe (knock on wood) that all the major components are there that it can start providing value and we can't wait to see people try it out.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I use it?
&lt;/h2&gt;

&lt;p&gt;The only thing you need to get started is to install the &lt;a href="https://github.com/marketplace/actions/meep-scanner"&gt;MEEP scanner&lt;/a&gt; action into a repo. Then, all commits with &lt;code&gt;[MEEP]&lt;/code&gt; in the commit message will be automatically indexed. Indexed MEEP requests will show up on the &lt;a href="https://moreeyesplz.com"&gt;website&lt;/a&gt; where people can browse and filter for requests they can provide feedback on. To kick things off, &lt;code&gt;themeepbot&lt;/code&gt; will write a message on your commit like so:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6PYsXa4K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/45qtqnwgnxpy39lladho.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6PYsXa4K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/45qtqnwgnxpy39lladho.PNG" alt="Alt Text" width="880" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Please give it a try, we have C++, Javascript, Game Dev, and more volunteers ready to provide feedback :). Also, if you're willing to participate as someone who would like to provide feedback as well, that would be awesome. &lt;em&gt;Keep in mind that the site works best on desktop right now.&lt;/em&gt; Making the site responsive for mobile/tablet is &lt;strong&gt;coming next&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How is feedback provided?
&lt;/h2&gt;

&lt;p&gt;Github supports linking to commits directly, where you can comment on specific lines of code, or comment in the general commit thread below. Don't forget to thank people who help you out!&lt;/p&gt;

&lt;h2&gt;
  
  
  Why did we write it?
&lt;/h2&gt;

&lt;p&gt;My &lt;a href="https://dev.to/duchesstoffee"&gt;wife&lt;/a&gt; made the brave decision to make a career switch to engineering a few months ago. She has loved &lt;a href="//dev.to"&gt;dev.to&lt;/a&gt; as a resource ever since she started, so when we saw the hackathon announcement, we started brainstormings projects we could work on together. One observation was that the community has &lt;em&gt;tons&lt;/em&gt; of knowledgeable and experienced engineers that are very generous with providing feedback. &lt;em&gt;More Eyes, Plz!&lt;/em&gt; was something we created to help streamline both requesting and providing feedback.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does it work?
&lt;/h2&gt;

&lt;p&gt;Actions, actions, and more actions. &lt;em&gt;More Eyes, Plz!&lt;/em&gt; runs almost completely off Github by leveraging actions. The data pipeline is as follows.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;em&gt;MEEP Scanner&lt;/em&gt; runs on every push. For each commit that contains &lt;code&gt;[MEEP]&lt;/code&gt;, the commit metadata is sent to a &lt;a href="https://cloud.google.com/functions"&gt;Google Cloud Function&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The cloud function dispatches &lt;em&gt;another&lt;/em&gt; action (called the "meeper") on a central repository, forwarding along the commit metadata as inputs by POSTing a &lt;a href="https://docs.github.com/en/rest/reference/actions#create-a-workflow-dispatch-event"&gt;&lt;code&gt;workflow_dispatch&lt;/code&gt; event&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;meeper&lt;/em&gt; action uses &lt;a href="https://docs.github.com/en/developers/apps/creating-a-github-app"&gt;Github app&lt;/a&gt; credentials associated with a bot we built called &lt;em&gt;themeepbot&lt;/em&gt;. The action authors a comment on the issuing commit (from step one) and also creates an issue on the &lt;a href="https://github.com/moreeyesplz/meeps/issues"&gt;meeps&lt;/a&gt; repository.&lt;/li&gt;
&lt;li&gt;The website uses the &lt;a href="https://docs.github.com/en/rest"&gt;Github REST API&lt;/a&gt; to query and search for issues, presenting them to users in a digestible way.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All components of the service (actions, website, tracker, bot) are open-sourced for the community to learn and benefit from. Feel free to browse the &lt;a href="https://github.com/moreeyesplz"&gt;moreeyesplz organization page&lt;/a&gt; on Github.&lt;/p&gt;

&lt;h2&gt;
  
  
  Please try it out and let us know what you think!
&lt;/h2&gt;

&lt;p&gt;If you run into issues, please file an issue on our &lt;a href="https://github.com/moreeyesplz/moreeyesplz/issues"&gt;general issue tracker&lt;/a&gt;. We wrote this in a weekend, and we already have tons of ideas on where to go next (leaderboard maybe? bot commands for closing out issues?).&lt;/p&gt;

&lt;h3&gt;
  
  
  Appendix: More implementation details!
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Using Cloud Functions
&lt;/h4&gt;

&lt;p&gt;The reason we couldn't build this service &lt;em&gt;entirely&lt;/em&gt; off of Github actions for security reasons. First, you definitely need some server-side code to run to authenticate OAuth tokens. In addition, we need to dispatch a workflow via REST API. This requires an access token with the &lt;code&gt;workflow&lt;/code&gt; permission which we can't ship as part of the scanner or provide to users (or anyone would be able to dispatch the workflow easily). Instead of maintaining servers, we wrote extremely lightweight cloud functions to easily sit inside the GCP free tier. The bulk of the the work is done by workflows to integrate with Github's APIs (creating commit comments, issues, querying data, etc).&lt;/p&gt;

&lt;h4&gt;
  
  
  Using the Issue Tracker as a Database
&lt;/h4&gt;

&lt;p&gt;Our favorite aspect of the design is the use of Github's fantastic issue tracker as a "database." One snag we initially ran into was that commit messages can contain arbitrary text (including unicode). This can make dealing with HTTP payload parsing being fussy (sending strings embedded in JSON payloads that contain quotation marks, braces, etc is an issue for example). The solution in a pinch is just to base64 encode the text and base64 decode it in the client. The benefit of using the issue tracker as a database is that we can easily interact with it in Github actions, which provides a robust querying interace.&lt;/p&gt;

&lt;h4&gt;
  
  
  Website Construction
&lt;/h4&gt;

&lt;p&gt;The website is a single-page application built using &lt;a href="https://www.typescriptlang.org/"&gt;TypeScript&lt;/a&gt;, &lt;a href="https://reactjs.org/"&gt;React&lt;/a&gt;, and &lt;a href="https://material-ui.com/"&gt;Material-UI&lt;/a&gt;. My wife designed and built the entire thing :). I think it's an amazing result for a weekend project considering she only started learning to code earlier this year. All data is fetched dynamically after the initial site load. Sign-in with Github is mandated to avoid rate limits hit when performing API requests without an access token. Feel free to follow the development of the website &lt;a href="https://github.com/moreeyesplz/moreeyesplz.github.io"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Bonus: Github Workflow that Deploys the Site
&lt;/h4&gt;

&lt;p&gt;The site is hosted via &lt;a href="https://pages.github.com/"&gt;Github Pages&lt;/a&gt;. To deploy the site, we simply kickoff a workflow which is defined &lt;a href="https://github.com/moreeyesplz/moreeyesplz.github.io/blob/master/.github/workflows/deploy.yml"&gt;here&lt;/a&gt;. This workflow could serve as the basis of other workflows that need to accomplish something similar. Some highlights:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;site&lt;/code&gt; branch was created with &lt;code&gt;git checkout --orphan&lt;/code&gt; to avoid branching off the main branch with source files&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;actions/cache@v2&lt;/code&gt; action is used to avoid fetching dependencies each time&lt;/li&gt;
&lt;li&gt;The workflow commits the built artifacts to the &lt;code&gt;site&lt;/code&gt; branch using the built-in credentials provided by &lt;code&gt;actions/checkout@v2&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Bot incoming!
&lt;/h4&gt;

&lt;p&gt;One workflow that we want to support is easily using commands to do things like close a meep, give kudos to people that were particularly helpful, edit meep labels, and more. Please stay tuned for future updates!&lt;/p&gt;

</description>
      <category>actionshackathon</category>
      <category>beginners</category>
      <category>codenewbie</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
