<?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>DEV Community: skywarth</title>
    <description>The latest articles on DEV Community by skywarth (@skywarth).</description>
    <link>https://dev.to/skywarth</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%2F187606%2F9c33e1f8-1126-45e2-8dfb-89ac77995166.jpeg</url>
      <title>DEV Community: skywarth</title>
      <link>https://dev.to/skywarth</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/skywarth"/>
    <language>en</language>
    <item>
      <title>How I built my first bot using Typescript - Part #4: Deployment and CI/CD</title>
      <dc:creator>skywarth</dc:creator>
      <pubDate>Fri, 30 Aug 2024 16:08:03 +0000</pubDate>
      <link>https://dev.to/skywarth/how-i-built-my-first-bot-using-typescript-part-4-deployment-and-cicd-3loa</link>
      <guid>https://dev.to/skywarth/how-i-built-my-first-bot-using-typescript-part-4-deployment-and-cicd-3loa</guid>
      <description>&lt;p&gt;Hi everyone,&lt;/p&gt;

&lt;p&gt;This the part #4 of the series, make sure to check the rest of them! Here's the &lt;a href="https://dev.to/skywarth/how-i-built-my-first-bot-using-typescript-part-3-code-coverage-reports-5958"&gt;previous episode&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Recently I've published a GitHub app on GitHub marketplace. &lt;a href="https://github.com/skywarth/darkest-PR" rel="noopener noreferrer"&gt;Darkest-PR&lt;/a&gt; is a bot for responding to the actions occurring in your repository by commenting them with quotes from the Darkest Dungeon game. The goal was to make development more fun and enjoyable thanks to the narrations by the Darkest-PR.&lt;/p&gt;

&lt;p&gt;In this episode, we'll discover the developments and decisions made for this app regarding the deployment and CI/CD aspects.&lt;/p&gt;

&lt;p&gt;Episodes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://dev.to/skywarth/how-i-built-my-first-bot-using-typescript-1hil"&gt;Beginning and architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/skywarth/how-i-built-my-first-bot-using-typescript-part-2-unit-testing-mocking-and-spying-1aka"&gt;Unit and feature testing, mocking &amp;amp; spying&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/skywarth/how-i-built-my-first-bot-using-typescript-part-3-code-coverage-reports-5958"&gt;Code coverage reports&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deployment and CI/CD (you're here!)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Publishing and final remarks&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Don't forget to check the &lt;a href="https://github.com/skywarth/darkest-PR" rel="noopener noreferrer"&gt;repository&lt;/a&gt; if you'd like to follow along!&lt;/p&gt;

&lt;h2&gt;
  
  
  What is CI/CD?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhileb56itnx7y2muj9by.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhileb56itnx7y2muj9by.png" alt="Image description" width="620" height="231"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;CI/CD stands for Continuous Integration and Continuous Deployment. It’s a methodology used to streamline the development process by automating the build, testing, and deployment stages of a project.&lt;/p&gt;

&lt;p&gt;Continuous Integration (CI): This is the practice of automatically integrating code changes into a shared repository several times a day. Each integration is verified by an automated build and tests, allowing me to catch issues early. Tools like GitHub Actions, Jenkins, and Travis CI are commonly used for CI.&lt;/p&gt;

&lt;p&gt;Continuous Deployment (CD): After the code passes all tests and meets quality checks, it’s automatically deployed to a production environment. This reduces manual intervention, making the deployment process faster and more reliable. Vercel, Netlify, and AWS Amplify are examples of CD tools.&lt;/p&gt;

&lt;p&gt;In the context of Darkest-PR:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CI tasks include:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Checking out the project (cloning) &lt;code&gt;uses: actions/checkout@v4&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Setting up Node.js environment &lt;code&gt;uses: actions/setup-node@v3&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Installing dependencies &lt;code&gt;npm ci&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Building the app &lt;code&gt;npm run build&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Running unit tests and generating coverage reports &lt;code&gt;npm test -- --coverage&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Uploading code coverage reports to CodeCov &lt;code&gt;uses: codecov/codecov-action@v4.0.1&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;CD tasks include:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deploying the app to Vercel as serverless deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setting Up CI with GitHub Actions
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F97a8mq0zjramyu46uapp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F97a8mq0zjramyu46uapp.png" alt="Image description" width="800" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GitHub Actions is the CI/CD tool I used for Darkest-PR. It allows me to automate tasks like testing and building the app whenever I push code to the repository.&lt;/p&gt;

&lt;p&gt;Here's the GitHub action workflow yaml file:&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="c1"&gt;# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node&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;Node.js CI&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;master"&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;master"&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;build&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;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;18.x&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="c1"&gt;# See supported Node.js release schedule at https://nodejs.org/en/about/releases/&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;actions/checkout@v4&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;Use Node.js ${{ matrix.node-version }}&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/setup-node@v3&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;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.node-version }}&lt;/span&gt;
        &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;npm'&lt;/span&gt;
    &lt;span class="pi"&gt;-&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 ci&lt;/span&gt;
    &lt;span class="pi"&gt;-&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 run build&lt;/span&gt;
    &lt;span class="pi"&gt;-&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 test -- --coverage&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;Upload coverage reports to Codecov&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;codecov/codecov-action@v4.0.1&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;token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.CODECOV_TOKEN }}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Let’s break down what this file does:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Trigger Events: The workflow triggers on any push or pull request to the master branch.&lt;/li&gt;
&lt;li&gt;Job Setup: It runs on an ubuntu-latest environment and tests the project using Node.js version 18.x.
Steps:&lt;/li&gt;
&lt;li&gt;Checkout Code: It pulls the latest code from the repository.&lt;/li&gt;
&lt;li&gt;Setup Node.js: It installs the specified Node.js version and caches npm dependencies.&lt;/li&gt;
&lt;li&gt;Install Dependencies: It installs the project dependencies using npm ci.&lt;/li&gt;
&lt;li&gt;Build the Project: It builds the source code.&lt;/li&gt;
&lt;li&gt;Run Tests: It runs tests and generates a coverage report.&lt;/li&gt;
&lt;li&gt;Upload Coverage: It uploads the coverage report to Codecov for analysis.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This setup ensures that the project is always tested and built in a clean environment, catching any issues before they reach production.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7rkqmro0fek0aql3hy89.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7rkqmro0fek0aql3hy89.png" alt="Image description" width="410" height="523"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying to Vercel
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb4zq0716zotuhus14ox4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb4zq0716zotuhus14ox4.png" alt="Image description" width="448" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For deployment, I use Vercel’s free tier, which offers free serverless deployment. The app is deployed by creating serverless function definitions that Vercel uses to run the app.&lt;/p&gt;

&lt;p&gt;Here’s the code from my deployment setup, Vercel API Endpoint: Located in /api/github/webhooks/index.ts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;createNodeMiddleware&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createProbot&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;probot&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../../../src/index.js&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;probot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createProbot&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;createNodeMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;probot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;webhooksPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/github/webhooks&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file defines the API endpoint that Vercel will deploy. It uses Probot to handle GitHub webhooks, allowing the app to listen to events from the repositories it’s installed on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fojnbcerezuoz82gbi42t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fojnbcerezuoz82gbi42t.png" alt="Image description" width="800" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Whenever I push code to the master branch, the CI workflow is triggered, running all tests and generating coverage reports. If everything passes, the app is then deployed to Vercel automatically, ensuring that the latest version is always live.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5nugzvtmd582tgo0nbiz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5nugzvtmd582tgo0nbiz.png" alt="Image description" width="800" height="389"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By integrating CI/CD into Darkest-PR, I’ve automated the build, testing, and deployment process, making the development workflow smoother and more efficient. GitHub Actions handles the CI tasks, while Vercel takes care of the CD side, providing a seamless deployment experience.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F89y25o3axoaukjwhq7uw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F89y25o3axoaukjwhq7uw.png" alt="Image description" width="800" height="528"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And it is a wrap! In the next chapter we will talk about the publishing of the app. GitHub Marketplace, ProductHunt and many more will be explored upon, so stay tuned.&lt;/p&gt;

</description>
      <category>cicd</category>
      <category>typescript</category>
      <category>vercel</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How I built my first bot using Typescript - Part #3: Code Coverage Reports</title>
      <dc:creator>skywarth</dc:creator>
      <pubDate>Wed, 21 Aug 2024 11:55:19 +0000</pubDate>
      <link>https://dev.to/skywarth/how-i-built-my-first-bot-using-typescript-part-3-code-coverage-reports-5958</link>
      <guid>https://dev.to/skywarth/how-i-built-my-first-bot-using-typescript-part-3-code-coverage-reports-5958</guid>
      <description>&lt;p&gt;Hi everyone,&lt;/p&gt;

&lt;p&gt;This the part #3 of the series, make sure to check the rest of them! &lt;a href="https://dev.to/skywarth/how-i-built-my-first-bot-using-typescript-part-2-unit-testing-mocking-and-spying-1aka"&gt;Here's the previous episode&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Recently I've published a GitHub app. &lt;a href="https://github.com/skywarth/darkest-PR" rel="noopener noreferrer"&gt;Darkest-PR&lt;/a&gt; is a bot for responding to the actions occurring in your repository by commenting them with quotes from the Darkest Dungeon game. The goal was to make development more fun and enjoyable thanks to the narrations by the Darkest-PR.&lt;/p&gt;

&lt;p&gt;For this episode, I'll be covering code coverage reports, deployment and CI/CD operations of the app. &lt;/p&gt;

&lt;p&gt;Episodes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://dev.to/skywarth/how-i-built-my-first-bot-using-typescript-1hil"&gt;Beginning and architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/skywarth/how-i-built-my-first-bot-using-typescript-part-2-unit-testing-mocking-and-spying-1aka"&gt;Unit and feature testing, mocking &amp;amp; spying&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Code coverage reports (you're here!)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Deployment and CI/CD&lt;/li&gt;
&lt;li&gt;Publishing and final remarks&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Don't forget to check the &lt;a href="https://github.com/skywarth/darkest-PR" rel="noopener noreferrer"&gt;repository&lt;/a&gt; if you'd like to follow along!&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Coverage
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is code coverage?
&lt;/h3&gt;

&lt;p&gt;Code coverage is a metric that is used to measure the percentage and the sections of code that are subject to unit/feature testing. If any of your unit tests hits a certain line in a file at any point during testing, then that line has been successfully covered by tests. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq6c45xep0ljfwm1a5ohu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq6c45xep0ljfwm1a5ohu.png" alt="Image description" width="800" height="281"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unit of code coverage is percentage (%). Higher it is, better it is. Because higher code coverage means sufficient amount of tests are prepared to cover those sections of the code. By my experience, for commercial applications your code coverage should be at least 50% , that is the vital minimum amount, anything below that means you're shooting in the dark and may encounter bugs at any time. For open-source projects and especially libraries, you should aim for around 95% coverage, 90% as minimum. Because libraries are meant to be used by hundreds if not thousands of projects, so if anything goes wrong in your library it will affects all those projects, teams and their users. So be a responsible open-source developer and prepare sufficient unit tests to reach at least %90 coverage. &lt;/p&gt;

&lt;p&gt;In theory; higher the code coverage amount, unlikely you're to encounter unexpected bugs. While high code coverage doesn’t guarantee bug-free code, it does give you confidence that a significant portion of your code is being tested, helping to catch potential issues early in the development process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Types of Code Coverage
&lt;/h3&gt;

&lt;p&gt;Before diving into the specifics of how we handle code coverage in Darkest-PR, let’s quickly go over the different types of code coverage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Line Coverage: Measures the percentage of lines of code that have been executed during tests.&lt;/li&gt;
&lt;li&gt;Branch Coverage: Focuses on the decision points in your code, ensuring that every possible branch (e.g., if/else statements) is tested.&lt;/li&gt;
&lt;li&gt;Function Coverage: Tracks whether each function in your codebase has been called during the testing process.&lt;/li&gt;
&lt;li&gt;Statement Coverage: Similar to line coverage but focuses on individual statements rather than lines.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How to generate code coverage reports
&lt;/h3&gt;

&lt;p&gt;There are various methods and frameworks for generating code coverage reports, it all depends on your tech stack. Some famous code coverage report generation tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;V8&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Javascript/Node.js&lt;/li&gt;
&lt;li&gt;Node.js built in test coverage engine&lt;/li&gt;
&lt;li&gt;Node.js has built-in V8 code coverage support, which generates detailed code coverage reports in JSON format. These reports can be converted to more readable formats (e.g., HTML) using tools like c8 or v8-to-istanbul.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Istanbul.js&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JavaScript/TypeScript&lt;/li&gt;
&lt;li&gt;Provides code coverage reports for JavaScript applications, directly generating detailed HTML, JSON, and text reports.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;PHPUnit&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PHP&lt;/li&gt;
&lt;li&gt;A testing framework for PHP that also generates code coverage reports in formats like HTML and XML.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Clover&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Java/Groovy&lt;/li&gt;
&lt;li&gt; Generates detailed code coverage reports for Java and Groovy projects, with output in HTML, XML, and JSON.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;And the list goes on. For each language there are dozens of test coverage engines to choose from. You should evaluate each one's pros and cons and deduce it per your requirements.&lt;/p&gt;

&lt;p&gt;For this project I went along with V8. I tried Istanbul engine too but I liked the performance and metrics of V8 more. Here's what output Vitest with V8 provides us in CLI:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgvph3x8shpwxjg82wdh5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgvph3x8shpwxjg82wdh5.png" alt="Image description" width="800" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the output above, we can clearly see which file has what percentage of coverage, and which files are lacking code coverage. &lt;/p&gt;

&lt;p&gt;When you run &lt;code&gt;vitest --coverage&lt;/code&gt; it will automatically create a &lt;code&gt;/coverage&lt;/code&gt; folder in your repository's root. This folder contains your coverage reports in various formats. XML, JSON and HTML. For demonstration purposes you may open the &lt;code&gt;index.html&lt;/code&gt; file in a browser to see the report yourself.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl5ihys9jr17g2srm7787.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl5ihys9jr17g2srm7787.png" alt="Image description" width="800" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also try &lt;code&gt;vitest --ui&lt;/code&gt; for interactive code coverage report webpage.&lt;/p&gt;

&lt;p&gt;Each of these code coverage reports (XML, JSON, HTML) contains the same coverage analysis but in different formats. In the next title, we'll explore how we can use these reports to publish them publicly and consistently.&lt;/p&gt;

&lt;h3&gt;
  
  
  SaaS tool for publishing coverage reports for free
&lt;/h3&gt;

&lt;p&gt;After being able to generate code coverage reports, now we can move on to publishing them on an online platform so these reports will be accessible anywhere anytime.&lt;/p&gt;

&lt;p&gt;&lt;a href="//codecov.io"&gt;CodeCov&lt;/a&gt; is a SaaS tool for uploading and visualizing your code coverage reports. It is free for open-source projects. It enables visually compelling charts to be displayed for your reports.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftaurx8euv9c5rx6abi5z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftaurx8euv9c5rx6abi5z.png" alt="Image description" width="800" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Moreover, CodeCov also offers support for Markdown badges so you can easily embed your coverage percentage to your repository.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff1fe4nubl9gmmqw33fds.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff1fe4nubl9gmmqw33fds.png" alt="Image description" width="203" height="130"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Simply login with your GitHub profile and add your repository to CodeCov. Afterwards you need to automatically relay code coverage reports whenever it is generated. Now you might ask: "Why, how am I supposed to send it to CodeCov if I generate it locally?" That is simple, you need to setup a CI/CD pipeline to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clone project&lt;/li&gt;
&lt;li&gt;Install dependencies&lt;/li&gt;
&lt;li&gt;Build the project&lt;/li&gt;
&lt;li&gt;Run tests and &amp;amp; generate code coverage reports&lt;/li&gt;
&lt;li&gt;Send generated coverage reports to CodeCov&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;CodeCov has a GitHub action to easily send reports, it is called &lt;code&gt;codecov/codecov-action&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here's the CI/CD action file for the project which reflects the steps mentioned above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Node.js CI

on:
  push:
    branches: [ "master" ]
  pull_request:
    branches: [ "master" ]

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [18.x]

    steps:
    - uses: actions/checkout@v4
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v3
      with:
        node-version: ${{ matrix.node-version }}
        cache: 'npm'
    - run: npm ci
    - run: npm run build
    - run: npm test -- --coverage
    - name: Upload coverage reports to Codecov
      uses: codecov/codecov-action@v4.0.1
      with:
       token: ${{ secrets.CODECOV_TOKEN }} #add your CodeCov secret to repository secrets!

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And after this, whenever a push occurs or a PR is created for master;  our code coverage reports will be submitted to CodeCov automatically and the metrics will always be up-to-date.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhswvyieux7fgm2agc6gp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhswvyieux7fgm2agc6gp.png" alt="Image description" width="540" height="548"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fizvv74eonf8fr70754j6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fizvv74eonf8fr70754j6.png" alt="Image description" width="800" height="285"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbx45b2a7r0kalje1s0ne.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbx45b2a7r0kalje1s0ne.png" alt="Image description" width="800" height="292"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>tutorial</category>
      <category>vitest</category>
      <category>node</category>
    </item>
    <item>
      <title>How I built my first bot using Typescript - Part #2: Unit testing, mocking and spying</title>
      <dc:creator>skywarth</dc:creator>
      <pubDate>Fri, 16 Aug 2024 00:30:26 +0000</pubDate>
      <link>https://dev.to/skywarth/how-i-built-my-first-bot-using-typescript-part-2-unit-testing-mocking-and-spying-1aka</link>
      <guid>https://dev.to/skywarth/how-i-built-my-first-bot-using-typescript-part-2-unit-testing-mocking-and-spying-1aka</guid>
      <description>&lt;p&gt;Hi everyone,&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This the part #2 of the series, make sure to check the rest of them!&lt;/strong&gt; &lt;a href="https://dev.to/skywarth/how-i-built-my-first-bot-using-typescript-1hil"&gt;Here's the first part.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Recently I've published a GitHub app. &lt;a href="https://github.com/skywarth/darkest-PR" rel="noopener noreferrer"&gt;Darkest-PR&lt;/a&gt; is a bot for responding to the actions occurring in your repository by commenting them with quotes from the Darkest Dungeon game. The goal was to make development more fun and enjoyable thanks to the narrations by the Darkest-PR.&lt;/p&gt;

&lt;p&gt;In this episode, we'll delve into unit testing and feature testing for my Probot app built with Typescript. We'll be using Vitest as testing framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  Episodes:
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://dev.to/skywarth/how-i-built-my-first-bot-using-typescript-1hil"&gt;Beginning and architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unit and feature testing, mocking &amp;amp; spying (you're here!)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/skywarth/how-i-built-my-first-bot-using-typescript-part-3-code-coverage-reports-5958"&gt;Code coverage reports&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Deployment and CI/CD&lt;/li&gt;
&lt;li&gt;Publishing and final remarks&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Don't forget to check the &lt;a href="https://github.com/skywarth/darkest-PR" rel="noopener noreferrer"&gt;repository&lt;/a&gt; if you'd like to follow along!&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Enacting TDD early on
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkfyujev4ta46ge2rgs4a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkfyujev4ta46ge2rgs4a.png" alt="Image description" width="489" height="511"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One thing I wanted to do in this project was to enact Test-Driven-Development early on, because in TDD you're meant to embrace it in the earlier phases of the project otherwise you wouldn't fully benefit from it. In TDD; you're meant to write tests first, then implement the feature. It's like shoot first ask questions later 😄, but in a better way. In TDD, when you write tests cases first, it is expected to be failing, since the feature is not implemented yet. It is the expected flow of TDD. Writing the unit/feature tests that are failing but includes all the necessary assertions per requirements of the feature. Then you write the implementation to accommodate the test requirements, to make it pass. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F878vd63ilgw6qidlzsla.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F878vd63ilgw6qidlzsla.png" alt="Image description" width="800" height="513"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hence I embraced TDD in this project, as early as when the first skeleton of the architecture took form. And what a great decision it was! Because at certain points I made drastic changes, altering decision, or encountered force-majeures which forced my hand to adjust my approach. In such cases I once again found the bliss of TDD. There wasn't tech debt or refactoring cost associated with changes because after each change I was able to run all tests easily and see whether any of the test cases were failing or not. This made the refactoring so much more productive than usual. Development costs were cut significantly. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu8uspop0ppi6rzygf4dk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu8uspop0ppi6rzygf4dk.png" alt="Image description" width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Structuring tests
&lt;/h2&gt;

&lt;p&gt;First thing I did before rushing for writing test cases, was to define a clean hierarchy and structure for tests. For starters I elected to have both unit and feature tests, but no integration tests. Also we are going to have certain test data, or 'fixtures' as it is usually called. It would also make sense to distinguish certain  modules/classes under distinct folders to categorize them. And the most important of them all, a boilerplate for tests. I usually prepare a boilerplate for my test cases in each project. Test case boilerplates helps you have some sort of inheritance/abstraction, enabling you to have control over all of the test, since they inherit or implement this boilerplate. We'll get to that later on don't worry, in the next section. And ta-da! Here's the final structure for tests&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F40a5c4tt37luvgqbou6i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F40a5c4tt37luvgqbou6i.png" alt="Image description" width="393" height="438"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deep dive into mocking and spying
&lt;/h2&gt;

&lt;p&gt;Prior to this project I've done many unit testing for various projects of mine. Different languages and frameworks. Now I wanted to see what Vitest offered in terms of mocking and spying. So bit by bit, demo by demo by I started the grasp the concept and how to apply it in Vitest. Here's my key-notes for them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mocking: you "mock" a function, alter/define its implementation to fit your agenda. Function, not method.&lt;/li&gt;
&lt;li&gt;Spying: you "spy" a method, method of a class or an object. Alter/define its implementation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mocking/spying allow you to register calls and responses. It also enables you to implement them numerous times as needed. It is actually such a strong capability.&lt;/p&gt;

&lt;p&gt;I've applied mocking and spying vigorously to see the full extent of its power.&lt;/p&gt;

&lt;p&gt;Here's the content of my unit testing stub (or abstract class) which contains all references to mocking and spying:&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StrategyTestSetup&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;probot&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Probot&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;quoteFacadeGetQuoteSpy&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MockInstance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;commentFactoryCreateSpy&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MockInstance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;actionStrategyHandleSpy&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MockInstance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;createCommentEndpointMock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Mock&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;vi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nl"&gt;pullRequestIndexResponseMock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Mock&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;vi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nl"&gt;pullRequestReviewIndexResponseMock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Mock&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;vi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nl"&gt;getConfigResponseMock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Mock&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;vi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nf"&gt;initializeMocks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;probot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Probot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;githubToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;Octokit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ProbotOctokit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defaults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;instanceOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&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="p"&gt;{&lt;/span&gt;
                    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;instanceOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="na"&gt;throttle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&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="nc"&gt;DarkestPR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;probot&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quoteFacadeGetQuoteSpy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spyOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;QuoteFacade&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;getQuote&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commentFactoryCreateSpy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spyOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CommentFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;create&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createCommentEndpointMock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mockImplementation&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;param&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&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;param&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pullRequestIndexResponseMock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mockImplementation&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;//default implementation&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getConfigResponseMock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mockImplementation&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;//default implementation&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;setupEndpointMocks&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;endpointRoot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test-owner&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;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test-repo&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;pullNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;555444&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="nf"&gt;nock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;endpointRoot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/repos/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;owner&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;repo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/pulls`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pullRequestIndexResponseMock&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nf"&gt;nock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;endpointRoot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/repos/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;owner&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;repo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/issues/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pullNumber&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/comments`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createCommentEndpointMock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&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;nock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;endpointRoot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/repos/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;owner&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;repo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/contents/.darkest-pr.json`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&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="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;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;content&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getConfigResponseMock&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="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="nf"&gt;nock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;endpointRoot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/repos/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;owner&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;repo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/pulls/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pullNumber&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/reviews`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pullRequestReviewIndexResponseMock&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;beforeAll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;nock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disableNetConnect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initializeMocks&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setupEndpointMocks&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;afterEach&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;vi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clearAllMocks&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;


    &lt;span class="nf"&gt;performCommonAssertions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expectedCaseSlug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):{&lt;/span&gt;&lt;span class="nl"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;}{&lt;/span&gt;
        &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actionStrategyHandleSpy&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveBeenCalled&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quoteFacadeGetQuoteSpy&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveBeenCalled&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commentFactoryCreateSpy&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveBeenCalled&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;commentInstance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commentFactoryCreateSpy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;commentInstance&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeInstanceOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;commentInstance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;caseSlug&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expectedCaseSlug&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;sentData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createCommentEndpointMock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
        &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createCommentEndpointMock&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveBeenCalledOnce&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sentData&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sentData&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;toBeTypeOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sentData&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="nf"&gt;toBeGreaterThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;commentInstance&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Base test class
&lt;/h2&gt;

&lt;p&gt;If you ever wrote unit/feat tests for a class/service, then you know the hassle about redundant code sections and declarations. Since Darkest-PR contains multiple strategy pattern implementations it was paramount to have a base test class which all other tests will extend from. This would enable centralized control over all the tests, and refactoring cost would be greatly reduced. Hence I prepared the &lt;code&gt;StrategyTestSetup&lt;/code&gt; base test class which is responsible for setting up test environment before commencing tests. You may have base test class, or stub, or abstract test class for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Common setup operations for test environment&lt;/li&gt;
&lt;li&gt;Clean-up operations after each test&lt;/li&gt;
&lt;li&gt;Assertions and evaluations that are common for each test&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For these reason, it is a great idea to have a base test class where all other (or certain group of) tests inherit or include from.&lt;/p&gt;

&lt;p&gt;Thanks to the &lt;code&gt;StrategyTestSetup&lt;/code&gt; base test class, now each of my strategy tests looks like this which is super simple and straight forward:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F565b19hr1ho54t5j71s8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F565b19hr1ho54t5j71s8.png" alt="Image description" width="800" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reducing duplicate code
&lt;/h2&gt;

&lt;p&gt;To eliminate duplicate code and redundancy, you should also use &lt;code&gt;.each()&lt;/code&gt; feature of Vitest whenever possible. &lt;code&gt;.each()&lt;/code&gt; can be applied to groups, &lt;code&gt;describe()&lt;/code&gt; statements and &lt;code&gt;test()&lt;/code&gt; statements. It is really flexible extension that allows you to loop tests and feed them predefined inputs. See it in action:&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="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Pull Request opened tests&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;strategyTestSetup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StrategyTestSetup&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nf"&gt;beforeAll&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;strategyTestSetup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;beforeAll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;afterEach&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;strategyTestSetup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;afterEach&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;No previous PRs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;previousPrs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
            &lt;span class="na"&gt;expectedCaseSlug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CaseSlugs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PullRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Opened&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Fresh&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="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Previously not merged (closed)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;previousPrs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pullRequestListNotMerged&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;expectedCaseSlug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CaseSlugs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PullRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Opened&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PreviouslyClosed&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="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Previously merged&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;previousPrs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pullRequestListMerged&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;expectedCaseSlug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CaseSlugs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PullRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Opened&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PreviouslyMerged&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$description&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;previousPrs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expectedCaseSlug&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="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Creates a comment after receiving the 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;async &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;strategyTestSetup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actionStrategyHandleSpy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spyOn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PullRequestOpenedStrategy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;handle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nx"&gt;strategyTestSetup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pullRequestIndexResponseMock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mockImplementation&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;previousPrs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;strategyTestSetup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;probot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;123&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pull_request&lt;/span&gt;&lt;span class="dl"&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;pullRequestOpenedPayload&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
            &lt;span class="nx"&gt;strategyTestSetup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;performCommonAssertions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expectedCaseSlug&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;strategyTestSetup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pullRequestIndexResponseMock&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveBeenCalledOnce&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And with these gains, and total of 90 test cases written, we conclude the test section of the project. In the next chapter we'll cover the CI/CD and test coverage aspect of the project so stay tuned!&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>tutorial</category>
      <category>vitest</category>
      <category>testing</category>
    </item>
    <item>
      <title>How I built my first bot using Typescript</title>
      <dc:creator>skywarth</dc:creator>
      <pubDate>Mon, 12 Aug 2024 13:21:15 +0000</pubDate>
      <link>https://dev.to/skywarth/how-i-built-my-first-bot-using-typescript-1hil</link>
      <guid>https://dev.to/skywarth/how-i-built-my-first-bot-using-typescript-1hil</guid>
      <description>&lt;p&gt;Hi everyone,&lt;/p&gt;

&lt;p&gt;Recently I've published a GitHub app. &lt;a href="https://github.com/skywarth/darkest-PR" rel="noopener noreferrer"&gt;Darkest-PR&lt;/a&gt; is a bot for responding to the actions occurring in your repository by commenting them with quotes from the Darkest Dungeon game. The goal was to make development more fun and enjoyable thanks to the narrations by the &lt;a href="https://github.com/skywarth/darkest-PR" rel="noopener noreferrer"&gt;Darkest-PR&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this post series, I'm going to explain how I built my first GitHub app, what was the important lessons that I've learnt, what secrets I've discovered and how you can do the same!&lt;/p&gt;

&lt;p&gt;These series will be separated into episodes/parts so you can follow the journey easily. &lt;/p&gt;

&lt;h2&gt;
  
  
  Episodes:
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Beginning and architecture (you're here!)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/skywarth/how-i-built-my-first-bot-using-typescript-part-2-unit-testing-mocking-and-spying-1aka"&gt;Unit and feature testing, mocking &amp;amp; spying&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/skywarth/how-i-built-my-first-bot-using-typescript-part-3-code-coverage-reports-5958"&gt;Code coverage reports&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/skywarth/how-i-built-my-first-bot-using-typescript-part-4-deployment-and-cicd-3loa"&gt;Deployment and CI/CD&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Publishing and final remarks&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Don't forget to check the &lt;a href="https://github.com/skywarth/darkest-PR" rel="noopener noreferrer"&gt;repository&lt;/a&gt; if you'd like to follow along!&lt;/p&gt;

&lt;h2&gt;
  
  
  Prologue
&lt;/h2&gt;

&lt;p&gt;Prior to this project I haven't practiced or used Typescript extensively. I knew how it operated and the differences of it with JS but never used it properly in a project. I've primarily used vanilla JS for years and had no problem with run-time debugging, type-safety or type-hinting. Therefor never really had a craving for Typescript.&lt;br&gt;
After deciding to build a GitHub app/bot, and discovering the Probot framework, during the installation I was asked whether to install framework for JS or Typescript. Since I wanted to experiment with it for a long while, I said hell why not and choose Typescript. I also wanted to experiment on the OOP and design pattern capabilities of the TS, to see it firsthand. So that's how and why I choose TS for this project.&lt;/p&gt;
&lt;h2&gt;
  
  
  Goal
&lt;/h2&gt;

&lt;p&gt;The goal was to develop a bot. I wanted this bot to respond with quotes specifically from Darkest Dungeon game. &lt;/p&gt;

&lt;p&gt;I've always enjoyed roguelike, dungeon-crawler and RPG games. Among all those games many, Darkest Dungeon has a special place in my heart. In no such game I've encountered such captivating, strong, profound and invested monologues. Both the audio and the narration scripts are spectacular. It is so Shakespearean. Another reason I keep it so dear is that it's stress, hope, despair, loss mechanics are unique. It really makes you connected with the game, as if you're feeling the actual moments your characters are going through, the ambiance is riveting. The first time you get party-wiped, you learn the true meaning of desperation and the setting of the game. It's like Dark Souls of dungeon crawlers.&lt;/p&gt;

&lt;p&gt;Fun fact: I unconsciously memorized 90% of all the quotes from Darkest Dungeon.&lt;/p&gt;

&lt;p&gt;In my career, I've reviewed thousands upon thousands pull requests. Countless issues, bugs tackled. Worked with my fellow teammates to undertake many challenging features. Each of these feature a different setting of emotions; some of them are definitive struggles, some a gentle breeze, some terrorizing nightmares, some are well-deserved relief after completing them.&lt;/p&gt;

&lt;p&gt;Then I realized, each development is very similar to Darkest Dungeon runs. Your team is your party, your environment is your location, your task/goal is your adversary. So it would be apt to narrate the development like so.&lt;/p&gt;

&lt;p&gt;And for this reason, Darkest-PR has come to life. To make development process more interactive, more fun, more story-like, an epic tale.&lt;/p&gt;
&lt;h2&gt;
  
  
  Humble beginnings
&lt;/h2&gt;

&lt;p&gt;After performing some research on how a GitHub app operates, without long I stumbled upon Probot. &lt;a href="https://probot.github.io/docs/" rel="noopener noreferrer"&gt;Probot&lt;/a&gt; is a framework for building GitHub applications using TS or JS. It has extensive documentation and handles all the nifty details which you might find daunting at first like: authentication, API keys, API security etc. After seeing how easy it would be using Probot, I decided to use it. &lt;/p&gt;

&lt;p&gt;I've initialized the project with &lt;code&gt;npx create-probot-app my-first-app&lt;/code&gt; command and filled in the questionnaire by the CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Let's create a Probot app!
? App name: Darkest-PR
? Description of app: A 'Hello World' GitHub App built with Probot.
? Author's full name: John Doe
? Author's email address: john@doe.com
? GitHub user or org name: skywarth
? Repository name: Darkest-PR
? Which template would you like to use? (Use arrow keys)
  basic-js
❯ basic-ts (use this one for TypeScript support)
  checks-js
  git-data-js
  deploy-js

Finished scaffolding files!

Installing dependencies. This may take a few minutes...

Successfully created my-first-app.

Begin using your app with:
  cd my-first-app
  npm start

View your app's README for more usage instructions.

Visit the Probot docs:
  https://probot.github.io/docs/

Get help from the community:
  https://probot.github.io/community/

Enjoy building your Probot app!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And there it was, the first step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;Being a software engineer, I have a strong appetite for designing the architecture of software systems. What are the requirements? What are the limitations? What do we strive for? Anyone who's ever done systems designs can tell what I'm talking about here. Functional and non-functional requirements of the system is to be determined. Later I've moved on to choose the infrastructure and establish data flow. This is high level design. Thanks to Probot, most of these are already established and decided for so there is not much of a high level design to be made.&lt;/p&gt;

&lt;p&gt;So I moved on to low-level-design, the LLD. My first priority here was to determine the technologies, platforms and most importantly the design patterns I'll be using. It is important to determine these beforehand, before you write a single line of code because they drastically change the course of the project. These are the patterns I've elected to go along with:&lt;/p&gt;

&lt;h3&gt;
  
  
  Repository pattern
&lt;/h3&gt;

&lt;p&gt;This is for the abstraction of data. Since 'quotes' in the app are constant and technically a data source, I decided to apply the principle of abstraction because even though currently it is loaded from a JSON file, I might decide to move them to a MySQL database, or a CouchDB, or a NoSQL database, or maybe even load them straight from variables in code. Who knows? To be prepared for these kind of challenges, and to make these transitions smooth you need to apply repository pattern. But keep in mind I didn't apply it just because of a 'if', I applied it because it is likely to change. Don't use patterns blindly or out of mere possibility, there should be a likelihood.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F293dei9bvvlaqd5dff31.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F293dei9bvvlaqd5dff31.png" alt="Image description" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Strategy pattern
&lt;/h3&gt;

&lt;p&gt;According to my requirement analysis, the app needs to respond to various events occurring in the user's GitHub repository. Such as PR opened, PR closed, Issue assignment, assignment removal, approval etc. Dozen events to begin with. And there could be more to come in the future. And to each of these events the app has to respond with a comment, that is the bottom line of it. Each event features a different data payload received from the webhook. Moreover, each of these events bear a different contextual emotion matrix so a fitting quote can be made for it. Essentially, each is a different strategy. Hence the strategy pattern. It is a good use-case for using strategy pattern. So I developed a structure and various abstraction to accommodate the need for various event handlers and in the end it fit perfectly for the mechanism. It allowed for a clean architecture and easy time debugging, changes were smooth if required.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwvudnqj9fgxwrqse415c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwvudnqj9fgxwrqse415c.png" alt="Image description" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fju9bf3knbcxw3ahrlcsa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fju9bf3knbcxw3ahrlcsa.png" alt="Image description" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Singleton pattern
&lt;/h3&gt;

&lt;p&gt;I gotta admit this was a bit of a blunder at first. Coming from other backend frameworks; life-cycle is different in NodeJS. NodeJS features a persistent state application, opposed to other backend frameworks like Laravel which has request based life-cycle. I didn't knew beforehand so it became a issue at first. I wanted to make certain components and modules singleton to prevent redundant fetching and loading operations, to reduce redundancy. For modules such as Config and QuoteRepository this makes sense, because why would you load config or quotes multiple times throughout the lifecycle if they are not subject to change? After this discovery of persistent state life-cycle, I altered singleton logic to accommodate the requirements. It is not a crucial pattern to have but I have a habit of using it when it is possible for optimization purposes. Beware that if you're not proficient with singleton pattern or the life-cycle of the infrastructure you're using, it is very likely you'll run into errors and bugs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fka8tsutm85y9kil0ghm5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fka8tsutm85y9kil0ghm5.png" alt="Image description" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Facade pattern
&lt;/h3&gt;

&lt;p&gt;A classic design pattern. My quote fetching and selection criteria are quite vast in the application, there are many different aspects to consider and evaluate, ordering and selection to be made based on the contextual emotion matrix of the situation. To abstract this level of complexity from higher level modules, I applied facade pattern to simplify the acquisition of quotes by the strategy patterns. QuoteFacade is consumed by the strategy pattern implementations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export class QuoteFacade{

    getQuote(actionContext:ActionContextDTO):Quote|undefined{

        //Case 1: if any other parameter along with slugs are provided: slugs and other filters will be applied separately and later be merged.
        //Case 2: if only slug param is provided, only will filter only by slugs.
        let quotes:QuoteCollection;

        if(actionContext.hasOnlyQuoteSlugs
        ){
            quotes=new QuoteCollection([]);
        }else{
            quotes=QuoteFacade.#quoteRepository.index();
        }

        if(actionContext.hasSentiment){
            //@ts-ignore
            quotes.filterBySentiment(actionContext.sentiment);
        }

        quotes.orderByCumulativeScoreDesc(actionContext.emotionMatrix,actionContext.tags);

        quotes.selectCandidates();

        if(actionContext.hasQuoteSlugs){//Damn this is ugly as hell
            let quotesBySlug=QuoteFacade.#quoteRepository.index().filterBySlugs(actionContext.quoteSlugs??[]);
            quotes.merge(quotesBySlug);
        }

        return quotes.shuffle().first();
    }

    getQuoteBySlug(slug:string):Quote|undefined{
        return QuoteFacade.#quoteRepository.find(slug);//Should we use the Repo's find or collection's find?
    }

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Chain of Responsibility pattern
&lt;/h3&gt;

&lt;p&gt;This one is really debatable and forced I'm aware. I wanted to implemented it because I've been seeking an opportunity to use it for a long time. Chain of responsibility is like a middleware structure, it passes or stops at the chains depending on assertions on each chain. My app configuration was dependent on both the BotConfig and RepositoryConfig of the user, I had to check and assert these whenever an action was due to happen, to simplify this I used chain-of-responsibility pattern. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F45bp48fyzgkkifdtkzwi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F45bp48fyzgkkifdtkzwi.png" alt="Image description" width="" height=""&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const botConfigSubsHandler=new EventSubscriptionHandler(BotConfig.getInstance());

const repoConfigSubsHandler=new EventSubscriptionHandler(repoConfig);
botConfigSubsHandler.nextHandler=repoConfigSubsHandler;

if (!botConfigSubsHandler.handle(this.getEventName())){
   return;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Factory pattern
&lt;/h3&gt;

&lt;p&gt;Even though we've abstracted the complexity by implementing Facade pattern for quotes, as QuoteFacade. This made strategy pattern implementations far simpler and cleaner, there wasn't anything in them that didn't belong there. But there was a small problem. Depending on the config, user may opt to see debug output or toggle emoji support. It is true we can easily obtain these settings from Config's but certain configs that are defined in the user's repository is bound to a event payload so we can fetch the repository config. This indicated a problem, QuoteFacade on it's own cannot do it because it doesn't have event payload context. And if we send it to the QuoteFacade by the strategies it would break SOLID principles and make testing so difficult. I resolved this by moving this requirement for config to a CommentFactory. CommentFactory takes a QuoteFacade instance and values for configs. This way the dependence between QuoteFacade, CommentFactory and Configs are broken, high achieved. Strategy pattern implementations were injected CommentFactory instances by the abstract strategy class and it was clean now.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const repositoryConfigPartial=await RepositoryConfig.readConfigFromRepository(this.ghContext as any as Context);
        const repoConfig=new RepositoryConfig(repositoryConfigPartial);

        const commentFactory=new CommentFactory(QuoteFacade.getInstance(),repoConfig.debug_mode,repoConfig.emojis);


const comment:Comment|null=await this.execute(commentFactory);//relaying to the child classes of the abstact strategy, each is responsible for the implementation of execute() method.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export class CommentFactory {

    #quoteFacade:QuoteFacade;
    #debugMode: boolean;
    #emojis: boolean;

    constructor(quoteFacade:QuoteFacade,debugMode: boolean, emojis: boolean) {
        this.#quoteFacade=quoteFacade;
        this.#debugMode = debugMode;
        this.#emojis = emojis;
    }

    get quoteFacade(): QuoteFacade {
        return this.#quoteFacade;
    }

    get debugMode(): boolean {
        return this.#debugMode;
    }

    get emojis(): boolean {
        return this.#emojis;
    }

    create(
        caseSlug: CaseSlugs.Types,
        actionContext: ActionContextDTO,
        replyToContext: ReplyContext | null = null,
        warnings: Array&amp;lt;string&amp;gt; = []
    ): Comment | null {
        const quote: Quote | undefined = this.quoteFacade.getQuote(actionContext);
        if (!quote) {
            return null;
        }
        return new Comment(
            quote,
            caseSlug,
            actionContext,
            this.debugMode,
            this.emojis,
            replyToContext,
            warnings
        );
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgninl1y19t6v3trawe71.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgninl1y19t6v3trawe71.png" alt="Image description" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Stay tuned for part #2 where we will be covering the episode of unit and feature testing. Thank you for reading!&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>github</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Ruin has come to your repository!</title>
      <dc:creator>skywarth</dc:creator>
      <pubDate>Sun, 11 Aug 2024 22:17:38 +0000</pubDate>
      <link>https://dev.to/skywarth/ruin-has-come-to-your-repository-3oo6</link>
      <guid>https://dev.to/skywarth/ruin-has-come-to-your-repository-3oo6</guid>
      <description>&lt;p&gt;Ever wished your GitHub repository could echo with the haunting, powerful narration of Darkest Dungeon's ancestor? Imagine your pull requests, issues, and comments narrated in that iconic voice, making every development decision feel like a step deeper into a dark, foreboding dungeon. Well, now you can!&lt;/p&gt;

&lt;p&gt;Introducing &lt;a href="https://github.com/skywarth/darkest-PR" rel="noopener noreferrer"&gt;Darkest-PR&lt;/a&gt;, GitHub app that transforms your repository into a thrilling adventure, filled with despair, hope, and everything in between. Whether you're looking to motivate your team, add a touch of humor, or simply make your workday more engaging, Darkest-PR is here to bring the ancestor's unique style to your development process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkciytorcx2lryvg1kq9v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkciytorcx2lryvg1kq9v.png" alt="Image description" width="800" height="202"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/skywarth/darkest-PR" rel="noopener noreferrer"&gt;Repository&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/marketplace/darkest-pr" rel="noopener noreferrer"&gt;GitHub Marketplace listing&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Darkest-PR?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/skywarth/darkest-PR" rel="noopener noreferrer"&gt;Darkest-PR&lt;/a&gt; is a GitHub bot that responds to repository events with contextual quotes from Darkest Dungeon. From pull request merges to issue assignments, Darkest-PR analyzes the situation and delivers a perfectly fitting quote. It’s like having the ancestor himself narrating the trials and triumphs of your development journey.&lt;/p&gt;

&lt;p&gt;Examples of How Darkest-PR Works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Someone is removed from PR assignment and the bot responds with:

&lt;ul&gt;
&lt;li&gt;&amp;gt; "Send this one to journey elsewhere, for we have need of sterner stock."&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;PR is rejected (request change) and the bot responds with:

&lt;ul&gt;
&lt;li&gt;&amp;gt; "Carelessness will find no clemency in this place!"&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;PR is closed without merge and the bot responds with:

&lt;ul&gt;
&lt;li&gt;&amp;gt; "A setback, but not the end of things!"&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;PR is accepted and merged immediately and the bot responds with:

&lt;ul&gt;
&lt;li&gt;&amp;gt; "A singular strike!"&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Someone is assigned to an issue and the bot responds with:

&lt;ul&gt;
&lt;li&gt;&amp;gt; "More arrive, foolishly seeking fortune and glory in this domain of the damned."&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyflhf3azocqk6q2ymli8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyflhf3azocqk6q2ymli8.png" alt="Image description" width="800" height="282"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These quotes aren't just for show—they add a layer of narrative depth, making every decision and event feel weightier, as if your team is a party of adventurers facing the unknown.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzcmu45fmil53hjqq64tq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzcmu45fmil53hjqq64tq.png" alt="Image description" width="800" height="205"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Use Darkest-PR?
&lt;/h2&gt;

&lt;p&gt;As developers, we often find ourselves dealing with the same routines such as: reviewing pull requests, closing issues, assigning tasks... Darkest-PR breaks this monotony by adding a unique, entertaining twist to these everyday tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Features:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Contextual Quotes: Automatically delivers quotes based on the event context, bringing a touch of drama or humor to your repository.&lt;/li&gt;
&lt;li&gt;Customizable: Tailor the bot's behavior to your needs with an optional configuration file.&lt;/li&gt;
&lt;li&gt;Interactive: Mention the bot directly in comments to get a personalized quote, or use parameters to specify the type of quote you want.&lt;/li&gt;
&lt;li&gt;Easy Installation: Get started in minutes, no complex setup required.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqght2bvr1zj3w34a6dmd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqght2bvr1zj3w34a6dmd.png" alt="Image description" width="800" height="332"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Get Started
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Go to the Darkest-PR's &lt;a href="https://github.com/apps/darkest-pr" rel="noopener noreferrer"&gt;GitHub App page&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Click 'Install' button&lt;/li&gt;
&lt;li&gt;Select the repositories you want to install the app&lt;/li&gt;
&lt;li&gt;Confirm the permission and click 'Install'&lt;/li&gt;
&lt;li&gt;Done! Enjoy.&lt;/li&gt;
&lt;li&gt;So now whenever a use-case event occurs, or whenever you mention the app like &lt;code&gt;@Darkest-PR&lt;/code&gt; it will respond to you!&lt;/li&gt;
&lt;li&gt;Give it a try, submit a comment in any issue/PR in your repository mentioning the app. E.g: &lt;code&gt;@Darkest-PR ancestor, do your thing.&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu4tlr7lttmbb2xlklwk6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu4tlr7lttmbb2xlklwk6.png" alt="Image description" width="800" height="282"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Join the Journey
&lt;/h2&gt;

&lt;p&gt;Darkest-PR is more than just a GitHub app—it’s an experience. Whether you’re a fan of Darkest Dungeon or simply looking for a way to make your development process more engaging, this app is for you.&lt;/p&gt;

&lt;p&gt;Start your journey with Darkest-PR today. Let the ancestor guide your team through the darkest depths of development. Install it now, star the repository, and share it with your fellow developers. Let's make coding an epic tale worth narrating!&lt;/p&gt;

&lt;p&gt;Built with love using Typescript and Probot framework. &lt;/p&gt;

</description>
      <category>github</category>
      <category>typescript</category>
      <category>productivity</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Are you using OpenAI API? Then you need to be prepared!</title>
      <dc:creator>skywarth</dc:creator>
      <pubDate>Fri, 05 Jul 2024 11:57:35 +0000</pubDate>
      <link>https://dev.to/skywarth/are-you-using-openai-api-then-you-need-to-be-prepared-2o60</link>
      <guid>https://dev.to/skywarth/are-you-using-openai-api-then-you-need-to-be-prepared-2o60</guid>
      <description>&lt;p&gt;If you are dependent on OpenAI API, you should know that at some point in time it might go down, even for a short period. And in such cases you would like to know, act accordingly or maybe even run certain automations to mitigate the problem at hand. Those that do monitoring on their external dependencies know exactly what I'm talking about. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This article is for those:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use OpenAI API in their apps, or have dependency on it directly/indirectly&lt;/li&gt;
&lt;li&gt;Prometheus Server and Blackbox exporter users&lt;/li&gt;
&lt;li&gt;Grafana, metrics and visualization enjoyers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Monitoring the status of APIs is crucial for maintaining the health and reliability of your applications. For those using OpenAI's API, the official status API always returns a 200 status code, even when the service is down. And naturally, this prevents you from probing this API via Prometheus Blackbox exporter.&lt;/p&gt;

&lt;p&gt;This is where the &lt;a href="https://github.com/skywarth/openai-api-status-prober" rel="noopener noreferrer"&gt;OpenAI API Status Prober&lt;/a&gt; comes in handy. It acts as a proxy, translating the status into meaningful HTTP codes that integrate seamlessly with your Prometheus setup.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frjqgongty9u71k0p3c0m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frjqgongty9u71k0p3c0m.png" alt="Image description" width="800" height="50"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnsocjx0y2nd7lh8g1um4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnsocjx0y2nd7lh8g1um4.png" alt="Image description" width="800" height="120"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Key Features&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accurate Status Reporting: Converts OpenAI's status API responses into proper HTTP codes (200/500/3xx).&lt;/li&gt;
&lt;li&gt;Easy Integration: Simplifies the process of integrating OpenAI API status monitoring into Prometheus.&lt;/li&gt;
&lt;li&gt;Flexible Installation Options: Supports global, local, and direct usage methods.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Use OpenAI API Status Prober?
&lt;/h2&gt;

&lt;p&gt;The primary motivation for using this tool is the limitation of the official OpenAI status API. By providing a proxy that returns appropriate HTTP status codes, the prober makes it possible to integrate OpenAI's status into Prometheus, enhancing your monitoring capabilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;You can install and set up OpenAI API Status Prober using three methods:&lt;/p&gt;

&lt;p&gt;1.Global Installation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install -g pm2
npm install -g openai-api-status-prober
openai-api-status-prober start
pm2 startup
pm2 save
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2.Local installation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/skywarth/openai-api-status-prober.git
cd openai-api-status-prober
npm ci
node src/server.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3.Direct Usage of Production Deployment:&lt;/p&gt;

&lt;p&gt;You can use the deployment directly via &lt;a href="https://openai-api-status-prober.onrender.com/open-ai-status-prober/simplified_status" rel="noopener noreferrer"&gt;https://openai-api-status-prober.onrender.com/open-ai-status-prober/simplified_status&lt;/a&gt;. However, it's recommended to self-host to avoid overloading the service.&lt;/p&gt;

&lt;h3&gt;
  
  
  Integrating into Prometheus Blackbox exporter
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;scrape_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;blackbox'&lt;/span&gt;
    &lt;span class="na"&gt;metrics_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/probe&lt;/span&gt;
    &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;module&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;http_2xx&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;static_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;http://127.0.0.1:9091/open-ai-status-prober/simplified_status&lt;/span&gt;
    &lt;span class="na"&gt;relabel_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source_labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;__address__&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;target_label&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;__param_target&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source_labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;__param_target&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;target_label&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;instance&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;target_label&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;__address__&lt;/span&gt;
        &lt;span class="na"&gt;replacement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;127.0.0.1:9115&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run &lt;code&gt;systemctl restart prometheus&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  CLI Commands
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Start Server: &lt;code&gt;openai-api-status-prober start&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Stop Server: &lt;code&gt;openai-api-status-prober stop&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Version: &lt;code&gt;openai-api-status-prober -v&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Env Path: &lt;code&gt;openai-api-status-prober env-path&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Repository: &lt;a href="https://github.com/skywarth/openai-api-status-prober" rel="noopener noreferrer"&gt;https://github.com/skywarth/openai-api-status-prober&lt;/a&gt;&lt;br&gt;
Deployment: &lt;a href="https://openai-api-status-prober.onrender.com/open-ai-status-prober/simplified_status" rel="noopener noreferrer"&gt;https://openai-api-status-prober.onrender.com/open-ai-status-prober/simplified_status&lt;/a&gt;&lt;/p&gt;

</description>
      <category>openai</category>
      <category>api</category>
      <category>prometheus</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>Apparently now I'm a trusted member!</title>
      <dc:creator>skywarth</dc:creator>
      <pubDate>Wed, 03 Jul 2024 11:33:07 +0000</pubDate>
      <link>https://dev.to/skywarth/apparently-now-im-a-trusted-member-2l4h</link>
      <guid>https://dev.to/skywarth/apparently-now-im-a-trusted-member-2l4h</guid>
      <description>&lt;p&gt;I just got an email that says now I'm a trusted member of the DEV.to, hooray!&lt;/p&gt;

&lt;h2&gt;
  
  
  What is trusted member?
&lt;/h2&gt;

&lt;p&gt;After a brief research and reading the guide, it essentially grants the user the permission to moderate the content and posts on DEV.to&lt;/p&gt;

&lt;p&gt;So basically you get these new permissions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Access to mod center&lt;/li&gt;
&lt;li&gt;Rank content quality&lt;/li&gt;
&lt;li&gt;Rate post's experience/proficiency levels&lt;/li&gt;
&lt;li&gt;Flag/report posts for spam or content that violates code of conduct&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How do one get it?
&lt;/h2&gt;

&lt;p&gt;The guide I've added below contains the answer to this but in essence, these are the crucial key points from it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Be kind, always&lt;/li&gt;
&lt;li&gt;Be helpful&lt;/li&gt;
&lt;li&gt;Give back to the community: share your experiences, do open-source etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're inclined on learning more, please visit this page for all the details and questions you might have: &lt;a href="https://dev.to/trusted-member"&gt;Trusted Member Guide&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Many thanks to DEV.to for granting this, I appreciate it. Happy to be part of this vibrant community.&lt;/p&gt;

&lt;p&gt;Credits:&lt;br&gt;
Cover splash art image is by John Cameron on &lt;a href="https://unsplash.com/photos/red-and-white-coca-cola-signage--_5IRj1F2rY" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>trusted</category>
      <category>member</category>
      <category>moderator</category>
      <category>devto</category>
    </item>
    <item>
      <title>Chaotic Schedule v1.1 released!</title>
      <dc:creator>skywarth</dc:creator>
      <pubDate>Sun, 26 May 2024 20:36:00 +0000</pubDate>
      <link>https://dev.to/skywarth/chaotic-schedule-v11-released-4e2</link>
      <guid>https://dev.to/skywarth/chaotic-schedule-v11-released-4e2</guid>
      <description>&lt;p&gt;Hello,&lt;/p&gt;

&lt;p&gt;I've released a new version for &lt;a href="https://github.com/skywarth/chaotic-schedule" rel="noopener noreferrer"&gt;Chaotic Schedule&lt;/a&gt; package. This new release introduces new random scheduling macro: &lt;code&gt;hourlyMultipleAtRandom().&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Chaotic Schedule?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/skywarth/chaotic-schedule" rel="noopener noreferrer"&gt;Github&lt;/a&gt;, &lt;a href="https://packagist.org/packages/skywarth/chaotic-schedule" rel="noopener noreferrer"&gt;Packagist&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Chaotic Schedule is a Laravel package which allows you to randomize command schedules, be it date or time. Want a sampler for taste of flavor, sure:&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$schedule-&amp;gt;command('foo:bar')&lt;br&gt;
-&amp;gt;weekly()&lt;br&gt;
-&amp;gt;randomDays(&lt;br&gt;
    RandomDateScheduleBasis::WEEK,&lt;br&gt;
    [Carbon::FRIDAY,Carbon::Tuesday,Carbon::Sunday],&lt;br&gt;
    1,2&lt;br&gt;
)&lt;br&gt;
-&amp;gt;atRandom('14:48','16:54');&lt;br&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Where can you use Chaotic Schedule?&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;Here's some use-cases which might be valid for you as well:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I have a command to send notifications to my clients. But I would like it to be sent at a random time between &lt;code&gt;14:00&lt;/code&gt; and &lt;code&gt;17:00&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;I would like to send some gifts to users if they are active between my special event period which is every week &lt;code&gt;Friday&lt;/code&gt; and &lt;code&gt;Saturday&lt;/code&gt; between &lt;code&gt;00:00&lt;/code&gt; and &lt;code&gt;04:20&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;My boss asked me to generate and send statistical reports regarding database activities every month, but only on &lt;code&gt;Monday&lt;/code&gt;, &lt;code&gt;Wednesday&lt;/code&gt; and &lt;code&gt;Friday&lt;/code&gt;. And this report has to be delivered in the morning between &lt;code&gt;08:00&lt;/code&gt; and &lt;code&gt;09:30&lt;/code&gt; and I want it to look like I've personally generated and sent it personally. So random time and date is crucial to stage this.&lt;/li&gt;
&lt;li&gt;I would like to send reminders to customers and I want it to look and feel &lt;em&gt;human&lt;/em&gt;. So random run times and dates every week would help me a lot. Otherwise, if I send every week on &lt;code&gt;Tuesday&lt;/code&gt; &lt;code&gt;11:00&lt;/code&gt; they would know this is automated and ignore these.&lt;/li&gt;
&lt;li&gt;There is a financial deficit, in order to detect the source of it I'll be running audit calculations. But these have to be random, otherwise they'll alter the records accordingly. I need to run audit calculations/assertions 3 times a day at random times.&lt;/li&gt;
&lt;li&gt;I'm trying to detect certain anomalies in my data, and therefore it would help me a lot to run a command completely randomly but with a minimum of at least 100 times a year.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's new?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;hourlyMultipleAtRandom()&lt;/code&gt; can be used for scheduling your commands to run every hour on random minutes. Example use case: I want to run a command every hour, 1-5 times at random, on random minutes. E.g. run minutes:[5,11,32,44]&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Runs every hour&lt;/li&gt;
&lt;li&gt;Only designates random &lt;strong&gt;run time(s)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Runs multiple times per hour, according to &lt;code&gt;$timesMin&lt;/code&gt; and &lt;code&gt;$timesMax&lt;/code&gt; params&lt;/li&gt;
&lt;li&gt;Doesn't designate any date on the schedule. So you may have to provide some date scheduling such as &lt;code&gt;daily()&lt;/code&gt;, &lt;code&gt;weekly()&lt;/code&gt;, &lt;code&gt;mondays()&lt;/code&gt; etc.&lt;/li&gt;
&lt;li&gt;Behaves exactly the same with &lt;a href="https://github.com/skywarth/chaotic-schedule?tab=readme-ov-file#hourly-at-random" rel="noopener noreferrer"&gt;-&amp;gt;hourlyAtRandom&lt;/a&gt; if the &lt;code&gt;timesMin=1&lt;/code&gt; and &lt;code&gt;timesMax=1&lt;/code&gt;. (I mean duh)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>opensource</category>
      <category>rng</category>
    </item>
    <item>
      <title>Stir some chaos into your schedules: Chaotic Schedule package</title>
      <dc:creator>skywarth</dc:creator>
      <pubDate>Sun, 31 Dec 2023 16:06:07 +0000</pubDate>
      <link>https://dev.to/skywarth/stir-some-chaos-into-your-schedules-chaotic-schedule-package-dnn</link>
      <guid>https://dev.to/skywarth/stir-some-chaos-into-your-schedules-chaotic-schedule-package-dnn</guid>
      <description>&lt;p&gt;Hello everyone, I would like to share a fresh Laravel Package I've developed with you. This package uses pRNG and cryptography methods to randomize scheduled command run intervals. &lt;/p&gt;

&lt;h1&gt;
  
  
  Chaotic Schedule
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://github.com/skywarth/chaotic-schedule" rel="noopener noreferrer"&gt;https://github.com/skywarth/chaotic-schedule&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Laravel package for randomizing command schedule intervals via pRNGs.&lt;/p&gt;

&lt;p&gt;Packagist: &lt;a href="https://packagist.org/packages/skywarth/chaotic-schedule" rel="noopener noreferrer"&gt;https://packagist.org/packages/skywarth/chaotic-schedule&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Consider the requirements&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PHP &amp;gt;=&lt;code&gt;7.4&lt;/code&gt; is required&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Install the package via composer:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require skywarth/chaotic-schedule
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;(optional)&lt;/code&gt; Publish the config in order to customize it
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; php artisan vendor:publish &lt;span class="nt"&gt;--provider&lt;/span&gt; &lt;span class="s2"&gt;"Skywarth&lt;/span&gt;&lt;span class="se"&gt;\C&lt;/span&gt;&lt;span class="s2"&gt;haoticSchedule&lt;/span&gt;&lt;span class="se"&gt;\P&lt;/span&gt;&lt;span class="s2"&gt;roviders&lt;/span&gt;&lt;span class="se"&gt;\C&lt;/span&gt;&lt;span class="s2"&gt;haoticScheduleServiceProvider"&lt;/span&gt; &lt;span class="nt"&gt;--tag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"config"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Done. You may now use random time and date macros on schedules&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Problem Definition
&lt;/h2&gt;

&lt;p&gt;Ever wanted to run your scheduled commands on random times of the day, or on certain days of the week? Or you may need to send some notifications not on fixed date times, but rather on random intervals hence it feels more &lt;em&gt;human&lt;/em&gt;. Then this is the package you're looking for.&lt;/p&gt;

&lt;p&gt;This Laravel packages enables you to run commands on random intervals and periods while respecting the boundaries set exclusively by you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Cases
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;I have a command to send notifications to my clients. But I would like it to be sent at a random time between &lt;code&gt;14:00&lt;/code&gt; and &lt;code&gt;17:00&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;I would like to send some gifts to users if they are active between my special event period which is every week &lt;code&gt;Friday&lt;/code&gt; and &lt;code&gt;Saturday&lt;/code&gt; between &lt;code&gt;00:00&lt;/code&gt; and &lt;code&gt;04:20&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;My boss asked me to generate and send statistical reports regarding database activities every month, but only on &lt;code&gt;Monday&lt;/code&gt;, &lt;code&gt;Wednesday&lt;/code&gt; and &lt;code&gt;Friday&lt;/code&gt;. And this report has to be delivered in the morning between &lt;code&gt;08:00&lt;/code&gt; and &lt;code&gt;09:30&lt;/code&gt; and I want it to look like I've personally generated and sent it personally. So random time and date is crucial to stage this.&lt;/li&gt;
&lt;li&gt;I would like to send reminders to customers and I want it to look and feel &lt;em&gt;human&lt;/em&gt;. So random run times and dates every week would help me a lot. Otherwise, if I send every week on &lt;code&gt;Tuesday&lt;/code&gt; &lt;code&gt;11:00&lt;/code&gt; they would know this is automated and ignore these. &lt;/li&gt;
&lt;li&gt;I'm trying to detect certain anomalies in my data, and therefore it would help me a lot to run a command completely randomly but with a minimum of at least 100 times a year.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>laravel</category>
      <category>schedule</category>
      <category>prng</category>
      <category>crypthography</category>
    </item>
    <item>
      <title>Deploying Your Vite Apps to Github Pages is a Breeze with Vite Github Pages Deployer</title>
      <dc:creator>skywarth</dc:creator>
      <pubDate>Sun, 18 Jun 2023 12:57:59 +0000</pubDate>
      <link>https://dev.to/skywarth/deploying-your-vite-apps-to-github-pages-is-a-breeze-with-vite-github-pages-deployer-30c3</link>
      <guid>https://dev.to/skywarth/deploying-your-vite-apps-to-github-pages-is-a-breeze-with-vite-github-pages-deployer-30c3</guid>
      <description>&lt;p&gt;Hello hello!&lt;/p&gt;

&lt;p&gt;If you're a developer using Vite for your Vue.js or React projects, and you're considering GitHub Pages for deploying your applications, then you've stumbled upon a gem. Today, I'm introducing you to a fresh open-source tool that is about to make your deployment process a piece of cake: Meet the Vite Github Pages Deployer!&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Vite Github Pages Deployer?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/skywarth/vite-github-pages-deployer" rel="noopener noreferrer"&gt;Vite Github Pages Deployer&lt;/a&gt; is an innovative open-source tool designed to significantly streamline the deployment of your Vite applications to Github Pages. &lt;/p&gt;

&lt;p&gt;Deploying via Github Actions feature is a beta feature, but provides an incredible new perspective on the deployment of your application! &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feetlsdghst02v8qz5uqs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feetlsdghst02v8qz5uqs.png" alt="Image description" width="451" height="293"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe9ne9hj9i97g4jk6992w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe9ne9hj9i97g4jk6992w.png" alt="Image description" width="800" height="194"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Vite Github Pages Deployer harnesses the power of GitHub Actions and Artifacts, providing a seamless, customizable deployment process that takes away the stress of manual deployments.&lt;/p&gt;

&lt;p&gt;Key features of Vite Github Pages Deployer include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zero manual/auto commits of the &lt;code&gt;dist&lt;/code&gt; folder or pushing to a branch.&lt;/li&gt;
&lt;li&gt;Seamless deployments to Github Pages leveraging actions and artifacts.&lt;/li&gt;
&lt;li&gt;A high degree of customization with optional build paths.
Clear error notifications and debug modes for hassle-free troubleshooting.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Usage Example
&lt;/h2&gt;

&lt;p&gt;Here's a quick sneak peek into how effortlessly you can incorporate this into your workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Vite Github Pages Deploy

on:
  # Runs on pushes targeting the default branch
  push:
    branches: ["master"]
  workflow_dispatch:

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
  contents: read
  pages: write
  id-token: write

jobs:
  # Build job
  build:
    runs-on: ubuntu-latest
    environment:
      name: demo
      url: ${{ steps.deploy_to_pages.outputs.github_pages_url }}
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Vite Github Pages Deployer
        uses: skywarth/vite-github-pages-deployer@master
        id: deploy_to_pages

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F84w21vta5bcoc9gi6wak.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F84w21vta5bcoc9gi6wak.png" alt="Image description" width="800" height="594"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm8zz5wo3nfp2siga336z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm8zz5wo3nfp2siga336z.png" alt="Image description" width="800" height="259"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A Must-Have For Vue.js and React Projects
&lt;/h2&gt;

&lt;p&gt;Whether you're whipping up a Vue.js application with Vite for a super speedy development experience, or a React project utilizing Vite's out-of-the-box React fast refresh and JSX support, Vite Github Pages Deployer will become your go-to deployment tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where can it be utilized?
&lt;/h2&gt;

&lt;p&gt;You need the &lt;a href="https://github.com/marketplace/actions/vite-github-pages-deployer" rel="noopener noreferrer"&gt;Vite Github Pages Deployer&lt;/a&gt; if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're creating a Vite application with Vue.js or React and want an uncomplicated, efficient deployment process to Github Pages.&lt;/li&gt;
&lt;li&gt;You're seeking an automated, reliable solution to save you from the monotony of manual deployments.&lt;/li&gt;
&lt;li&gt;You're developing open-source projects and would like to showcase it free-of-charge.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1mlv0kpo61vnmwtuf8m4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1mlv0kpo61vnmwtuf8m4.png" alt="Image description" width="800" height="185"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Give it a go!
&lt;/h2&gt;

&lt;p&gt;Embark on a simplified journey with Vite Github Pages Deployer and discover how it can revolutionize your deployment process, transforming it into an effortless task. Check out the &lt;a href="https://github.com/skywarth/country-routing-algorithm-demo-vue" rel="noopener noreferrer"&gt;demo project&lt;/a&gt; to witness it in action.&lt;/p&gt;

&lt;p&gt;As always, contributions of any kind is welcome. Reviews, suggestions, PRs and issues are appreciated. &lt;/p&gt;

&lt;p&gt;And lastly, if you find Vite Github Pages Deployer beneficial, show your support by giving a ⭐ to the repository. &lt;/p&gt;

&lt;p&gt;Enjoy!&lt;/p&gt;

</description>
      <category>vite</category>
      <category>vue</category>
      <category>githubpages</category>
      <category>githubactions</category>
    </item>
    <item>
      <title>Simulating wolf pack in JS with Fenrir</title>
      <dc:creator>skywarth</dc:creator>
      <pubDate>Fri, 28 Jun 2019 11:34:17 +0000</pubDate>
      <link>https://dev.to/skywarth/simulating-wolf-pack-in-js-with-fenrir-40h8</link>
      <guid>https://dev.to/skywarth/simulating-wolf-pack-in-js-with-fenrir-40h8</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/skywarth/Fenrir-wolfpack-simulator" rel="noopener noreferrer"&gt;https://github.com/skywarth/Fenrir-wolfpack-simulator&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fenrir is an assistant to prognosticate the future of a wolfpack using vanilla Javascript and data structures.&lt;br&gt;
Simulation and prediction includes:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Changes in the environment
Health and status of individual wolves
Disease conditions of each wolf
Weather events and effects
Alpha rankings in the pack
Mortality status of pack members
Wild card encounters (triggered by scout event)
Meat distribution
Regular events
Irregular events
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>simulation</category>
      <category>javascript</category>
      <category>html</category>
      <category>jquery</category>
    </item>
  </channel>
</rss>
