<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Rohit's Blog's RSS Feed]]></title><description><![CDATA[Hi! I'm a full-stack javascript developer interested in open-source, dev-ops, ci-cd, developer productivity, doing things "right", and writing things down.
Hoping to pen down my thoughts through this blog.]]></description><link>https://rohit.page</link><generator>GatsbyJS</generator><lastBuildDate>Tue, 20 Feb 2024 22:15:37 GMT</lastBuildDate><item><title><![CDATA[Jira CI/CD Integration]]></title><description><![CDATA[Ever wanted to get build and deployment links directly in Jira? Now you can, just add this small step to the last stage of your pipeline and…]]></description><link>https://rohit.page/blog/projects/jira-ci-cd-integration-for-builds-deploys/</link><guid isPermaLink="false">https://rohit.page/blog/projects/jira-ci-cd-integration-for-builds-deploys/</guid><pubDate>Sun, 08 Aug 2021 05:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Ever wanted to get build and deployment links directly in Jira? Now you can, just add this small step to the last stage of your pipeline and get all the relevant information right in the ticket details page.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/rohit-gohri/jira-ci-cd-integration/main/docs/builds-panel.png&quot; alt=&quot;Builds Panel Preview&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Generate Credentials for OAuth App&lt;/h2&gt;
&lt;p&gt;Generate new OAuth Credentials and copy&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;See:&lt;/strong&gt; &lt;a href=&quot;https://support.atlassian.com/jira-cloud-administration/docs/integrate-with-self-hosted-tools-using-oauth/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://support.atlassian.com/jira-cloud-administration/docs/integrate-with-self-hosted-tools-using-oauth/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/rohit-gohri/jira-ci-cd-integration/main/docs/oauth-creds.png&quot; alt=&quot;OAuth Creds Screen&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Use with Any CI/CD Provider&lt;/h2&gt;
&lt;p&gt;As long as your CI/CD provider supports running Docker images you may use this integration. Tested in Drone.io, Gitlab CI, and Google Cloud Build.&lt;/p&gt;
&lt;p&gt;Docker Images are available from:&lt;/p&gt;
&lt;div class=&quot;no-border&quot;&gt;
&lt;ul&gt;
&lt;li&gt;Github Container Registry: &lt;a href=&quot;https://github.com/-/rohit-gohri/packages/container/package/jira-ci-cd-integration&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;ghcr.io/rohit-gohri/jira-ci-cd-integration&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Gitlab Container Registry: &lt;a href=&quot;https://gitlab.com/rohit-gohri/jira-ci-cd-integration/container_registry/2072732&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;registry.gitlab.com/rohit-gohri/jira-ci-cd-integration&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Docker Hub: &lt;a href=&quot;https://hub.docker.com/r/boringdownload/jira-integration&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;boringdownload/jira-integration&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;Pick whatever you want and is convenient for you.&lt;/p&gt;
&lt;p&gt;Configuration is through Environment Variables. Read more in the &lt;a href=&quot;https://github.com/rohit-gohri/jira-ci-cd-integration#options&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;repository Readme&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Jira Drone.io Example&lt;/h3&gt;
&lt;span class=&quot;no-border&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://cloud.drone.io/rohit-gohri/jira-ci-cd-integration&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img src=&quot;https://cloud.drone.io/api/badges/rohit-gohri/jira-ci-cd-integration/status.svg?ref=refs/tags/v0&quot; alt=&quot;Build Status&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.drone.io/pipeline/environment/syntax/#from-secrets&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Add secrets&lt;/a&gt; for &lt;code&gt;JIRA_CLIENT_ID&lt;/code&gt; and &lt;code&gt;JIRA_CLIENT_SECRET&lt;/code&gt; and then add this to your pipeline:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;steps:
  - name: jira-integration
    image: boringdownload/jira-integration:v0
    environment:
      BUILD_NAME: drone-pipeline
      JIRA_INSTANCE: companyname
      JIRA_CLIENT_ID:
        from_secret: jira_client_id
      JIRA_CLIENT_SECRET:
        from_secret: jira_client_secret
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Jira Gitlab CI/CD&lt;/h4&gt;
&lt;span class=&quot;no-border&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://gitlab.com/rohit-gohri/jira-ci-cd-integration/-/commits/v0&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img src=&quot;https://gitlab.com/rohit-gohri/jira-ci-cd-integration/badges/v0/pipeline.svg&quot; alt=&quot;pipeline status&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.gitlab.com/ee/ci/variables/#add-a-cicd-variable-to-a-project&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Add a CI/CD Variable to your project&lt;/a&gt; for &lt;code&gt;JIRA_CLIENT_ID&lt;/code&gt; and &lt;code&gt;JIRA_CLIENT_SECRET&lt;/code&gt; (remember to mask them) and then add these steps to your pipeline (we use &lt;code&gt;.post&lt;/code&gt; stage so it runs last)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;jira-integration-on-success:
  stage: .post
  when: on_success
  image: registry.gitlab.com/rohit-gohri/jira-ci-cd-integration:v0
  script: jira-integration
  variables:
    BUILD_STATE: successful
    BUILD_NAME: gitlab-pipeline
    JIRA_INSTANCE: companyname

jira-integration-on-failure:
  extends: jira-integration-on-success
  when: on_failure
  variables:
    BUILD_STATE: failure
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Usage With Github Actions&lt;/h3&gt;
&lt;p&gt;You can also use this as a Github Action if you want to send release information to Jira.&lt;/p&gt;
&lt;span class=&quot;no-border&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/rohit-gohri/jira-ci-cd-integration/actions/workflows/test-release.yml&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img src=&quot;https://github.com/rohit-gohri/jira-ci-cd-integration/actions/workflows/test-release.yml/badge.svg&quot; alt=&quot;test-release workflow&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;p&gt;First add OAuth Creds as secrets to Github (&lt;strong&gt;See:&lt;/strong&gt; &lt;a href=&quot;https://docs.github.com/en/actions/reference/encrypted-secrets&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://docs.github.com/en/actions/reference/encrypted-secrets&lt;/a&gt;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add Client ID as &lt;code&gt;JIRA_CLIENT_ID&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Add Client Secret as &lt;code&gt;JIRA_CLIENT_SECRET&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/rohit-gohri/jira-ci-cd-integration/main/docs/github-secrets.png&quot; alt=&quot;Github Secrets&quot;&gt;&lt;/p&gt;
&lt;h4&gt;Use in Builds Pipeline&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: Jira Integration
  if: ${{ always() }}
  uses: rohit-gohri/jira-ci-cd-integration@v0
  with:
    event_type: build
    state: ${{ job.status }}
    jira_instance: companyname # Subdomain for Jira Cloud
    client_id: ${{ secrets.JIRA_CLIENT_ID }}
    client_secret: ${{ secrets.JIRA_CLIENT_SECRET }}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Use in Deployment Pipeline&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: Jira Integration
  if: ${{ always() }}
  uses: rohit-gohri/jira-ci-cd-integration@v0
  with:
    event_type: deployment
    state: ${{ job.status }}
    issue: JCI-3, JCI-6 # Comma separated list of issues being deployed/released. You are expected to generate this yourself in a previous step
    jira_instance: companyname # Subdomain for Jira Cloud
    client_id: ${{ secrets.JIRA_CLIENT_ID }}
    client_secret: ${{ secrets.JIRA_CLIENT_SECRET }}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Result&lt;/h2&gt;
&lt;p&gt;That&apos;s all it takes. You&apos;ll get Builds information like you do for Branches and Pull Requests:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/projects/jira-ci-cd/result.png&quot; alt=&quot;result&quot;&gt;&lt;/p&gt;
&lt;div class=&quot;no-border&quot;&gt;
&lt;blockquote&gt;
&lt;p&gt;Image: &lt;a href=&quot;https://thenounproject.com/term/continuous-deployment/2661846/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;continuous deployment by Shocho from the Noun Project&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/div&gt;</content:encoded></item><item><title><![CDATA[My Favourite Helper Types in Typescript]]></title><description><![CDATA[Over the past few years of using Typescript and slowly getting comfortable writing complex custom types, I have amassed a collection of…]]></description><link>https://rohit.page/blog/posts/my-favourite-helper-types-in-typescript/</link><guid isPermaLink="false">https://rohit.page/blog/posts/my-favourite-helper-types-in-typescript/</guid><pubDate>Wed, 27 Jan 2021 19:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Over the past few years of using Typescript and slowly getting comfortable writing complex custom types, I have amassed a collection of small helper types from around the internet. Here are a few of them that I end up adding to almost all my major projects and how I use them:&lt;/p&gt;
&lt;h2&gt;Unpromise&lt;/h2&gt;
&lt;p&gt;Sometimes you need to get the resolved value of a &lt;code&gt;Promise&amp;#x3C;T&gt;&lt;/code&gt; wrapped type, mostly when using along with the inbuilt &lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/utility-types.html#returntypetype&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;ReturnType&lt;/code&gt;&lt;/a&gt; type:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;type Unpromise&amp;#x3C;T&gt; = T extends Promise&amp;#x3C;infer U&gt; ? U : T;

type a = Promise&amp;#x3C;number&gt;;
type x = Unpromise&amp;#x3C;a&gt;; // number
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use with &lt;code&gt;ReturnType&lt;/code&gt; to make &lt;code&gt;ReturnTypeResolved&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;type ReturnTypeResolved&amp;#x3C;T extends (...args: any) =&gt; any&gt; = Unpromise&amp;#x3C;
  ReturnType&amp;#x3C;T&gt;
&gt;;

function test(): Promise&amp;#x3C;{ weird: &quot;value&quot; }&gt; {
  return Promise.resolve({ weird: &quot;value&quot; });
}

type WeirdType = ReturnTypeResolved&amp;#x3C;typeof test&gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;span class=&quot;no-border&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://www.typescriptlang.org/play?ssl=12&amp;#x26;ssc=37&amp;#x26;pln=12&amp;#x26;pc=43#code/C4TwDgpgBAqgdmATgewLYEsDOEA8AVAPigF4o8oIAPYCOAE0ygAUUNsd04AzCRWIgPywoALjIBuAFCTQkKAEMSzVllxwArqgBGvAlNnRKS+EjSqc8vVAD01qBu29pBqACUIwdYjh5wEd5jIADYAbhB0+BTUtAxQABQAdEnyiADmmGLycCAAlCREWSBEpCYq7O6e3r6Q+AR60lzqcADGwOjIcFA0mMBxOWIsZuwA3gDuEOiIdGIA5CHyQeoQMwC+RMOSUFCIHl6dg2wQCTuBoRBxYxNTs-OLyys5UivOflAA6ld01dCkFXvfAWCYQiBmQXC6EB6eiAA&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img src=&quot;https://img.shields.io/badge/Open_in_Playground-555555.svg?style=flat-square&amp;#x26;logo=typescript&amp;#x26;labelColor=3178c6&amp;#x26;logoColor=fff&quot; alt=&quot;Open in TS Playground&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;h2&gt;PlainObject&lt;/h2&gt;
&lt;p&gt;I know we have the inbuilt &lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeystype&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;Record&lt;/code&gt;&lt;/a&gt; but this is such a common and &lt;strong&gt;frequent&lt;/strong&gt; use case that I like having this around:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;type PlainObject&amp;#x3C;T = any&gt; = Record&amp;#x3C;string, T&gt;;

type NumMap = PlainObject&amp;#x3C;number&gt;;
const a: NumMap = {
  key1: 1,
  key2: 2,
  keyx: &quot;x&quot;, // ERROR: expected number
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also create a reverse of this, &lt;code&gt;ValueOf&amp;#x3C;T&gt;&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;type ValueOf&amp;#x3C;T&gt; = T[keyof T];

type x = ValueOf&amp;#x3C;NumMap&gt;; // number
&lt;/code&gt;&lt;/pre&gt;
&lt;span class=&quot;no-border&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://www.typescriptlang.org/play?#code/C4TwDgpgBACgNgQwJYDsDyAjAVhAxsAHgBUoBeKBFEAPjKgCU8B7AJwBMCBnYF1AcwA0UItQDcAKHGhIUAHIBXALYBZBGDrxk6bHkIolGCCzHjcTFNwoAuOUtXryAb3FQoAawggAjDa8CX7p4ATDZB-q4eIAAeNgDkUbFCAPRJUACi9PRo9DYQUZD4EGxQ+oqGLOIAvhJS4NAAaghw8hBoAGbEtOREANqRTG3CALo10tBRdI3NrR0KKmpiUCklBkbiQA&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img src=&quot;https://img.shields.io/badge/Open_in_Playground-555555.svg?style=flat-square&amp;#x26;logo=typescript&amp;#x26;labelColor=3178c6&amp;#x26;logoColor=fff&quot; alt=&quot;Open in TS Playground&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;h2&gt;RecursivePartial&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;Partial&lt;/code&gt;&lt;/a&gt; but for nested objects, most useful when declaring defaults or merging some overrides for a config type:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;type RecursivePartial&amp;#x3C;T&gt; = {
  [P in keyof T]?: T[P] extends (infer U)[]
    ? RecursivePartial&amp;#x3C;U&gt;[]
    : T[P] extends object
    ? RecursivePartial&amp;#x3C;T[P]&gt;
    : T[P];
};

const config = {
  a: {
    x: {
      p: 1,
      q: 2,
    },
    y: &quot;nested&quot;,
    z: &quot;value&quot;,
  },
  b: &quot;root&quot;,
  c: &quot;value&quot;,
};

const configOverride: RecursivePartial&amp;#x3C;typeof config&gt; = {
  a: {
    x: {
      p: &quot;wrong&quot;, // should be number
    },
  },
};
&lt;/code&gt;&lt;/pre&gt;
&lt;span class=&quot;no-border&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://www.typescriptlang.org/play?#code/C4TwDgpgBAShDGBXATgZwJYDcIAUCGyw6eANgDwAqAfFALxQDeAUFFANo5ToB2UA1hBAB7AGZQKAXQD8ALhatxHCVAgAPYBG4ATVFAAUPERGRQAqgEo2yqbAQoM2fIWLlTVK1DkLFOZWo3aukIARgBWCMBQNnBIaFi4BESklEo0XgoUSgDcTAC+OUzwQtyokUXcIugA5nSM8ngydd6qjczerGCNAIwANPLeAI6NAEx93rljCiCNAOTcEKUQWjOTrABes5ikiBAr8hPywbPIQkLAe6zwm9u7fflMhcWlUOWVVQDy2MjI6FoQjTF7PEnElyKBIKIXsU3jR6G0oA0mgoWkjvJ0oDMAO4nbhVFZQAD0BKgqAAFkJECQtFBgtBuIgALa05D9KAHVgHfJAA&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img src=&quot;https://img.shields.io/badge/Open_in_Playground-555555.svg?style=flat-square&amp;#x26;logo=typescript&amp;#x26;labelColor=3178c6&amp;#x26;logoColor=fff&quot; alt=&quot;Open in TS Playground&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/span&gt;
&lt;h2&gt;Also See&lt;/h2&gt;
&lt;p&gt;Go over the inbuilt utility types in the &lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/utility-types.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Typescript Docs&lt;/a&gt;. And checkout this great library, &lt;a href=&quot;https://github.com/piotrwitek/utility-types&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;utility-types&lt;/a&gt; on Github for other common types people tend to use.&lt;/p&gt;
&lt;h3&gt;Credits&lt;/h3&gt;
&lt;p&gt;Links to the various places I originally found these from:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Unpromise&lt;/code&gt;: Sourced from this StackOverflow Answer: &lt;a href=&quot;https://stackoverflow.com/a/53196435/9485498&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://stackoverflow.com/a/53196435/9485498&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RecursivePartial&lt;/code&gt;: Sourced from this StackOverflow Answer: &lt;a href=&quot;https://stackoverflow.com/a/51365037/9485498&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://stackoverflow.com/a/51365037/9485498&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[OpenAPI for Docusaurus! 📘]]></title><description><![CDATA[Introducing "Redocusaurus": Redoc for Docusaurus Redocusaurus npm version npm downloads What is it? It is a preset for docusaurus, with…]]></description><link>https://rohit.page/blog/projects/openapi-for-docusaurus/</link><guid isPermaLink="false">https://rohit.page/blog/projects/openapi-for-docusaurus/</guid><pubDate>Tue, 19 Jan 2021 17:30:00 GMT</pubDate><content:encoded>&lt;p&gt;Introducing &quot;Redocusaurus&quot;: &lt;a href=&quot;https://github.com/redocly/redoc&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Redoc&lt;/a&gt; for &lt;a href=&quot;https://v2.docusaurus.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Docusaurus&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;https://npmjs.com/package/redocusaurus/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Redocusaurus&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://img.shields.io/npm/v/redocusaurus?style=flat-square&quot; alt=&quot;npm version&quot;&gt; &lt;img src=&quot;https://img.shields.io/npm/dw/redocusaurus?style=flat-square&quot; alt=&quot;npm downloads&quot;&gt;&lt;/p&gt;
&lt;h2&gt;What is it?&lt;/h2&gt;
&lt;p&gt;It is a preset for docusaurus, with 2 main components:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/rohit-gohri/redocusaurus/tree/main/packages/docusaurus-theme-redoc&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;docusaurus-theme-redoc&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Using Redoc as a React component, this provides 2 theme components, &lt;code&gt;@theme/ApiDoc&lt;/code&gt; and &lt;code&gt;@theme/Redoc&lt;/code&gt;. You can use them directly in js pages in Docusaurus.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/rohit-gohri/redocusaurus/tree/main/packages/docusaurus-plugin-redoc&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;docusaurus-plugin-redoc&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This creates routes from your OpenAPI files or URLs and renders with the components provided by the theme.&lt;/p&gt;
&lt;h2&gt;How to set it up?&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install the preset&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;npm i --save redocusaurus
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add it to your &lt;code&gt;docusaurus.config.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;module.exports = {
 // ...
 presets: [
   // ...
   [
     &apos;redocusaurus&apos;,
     {
       // Plugin Options for loading OpenAPI files
       specs: [
         {
           spec: &apos;https://redocly.github.io/redoc/openapi.yaml&apos;,
           route: &apos;/api/&apos;,
         },
       ],
       // Theme Options for modifying how redoc renders them
       theme: {
         // Change with your site colors
         primaryColor: &apos;#1890ff&apos;,
       },
     },
   ],
 ],
 // ...
};
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Rebuild your site and you will have your OpenAPI docs available at the &lt;code&gt;/api/&lt;/code&gt; route.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Extra features over directly using Redoc&lt;/h2&gt;
&lt;p&gt;The theme includes redoc settings and some custom css to make it match the &lt;code&gt;@docusaurus/classic&lt;/code&gt; theme. Along with support for &lt;strong&gt;dark mode&lt;/strong&gt;.&lt;/p&gt;
&lt;video autoplay loop muted playsinline&gt;
  &lt;source src=&quot;/videos/video_2021-01-19_22-56-46.mp4&quot; type=&quot;video/mp4&quot;&gt;
  &lt;img src=&quot;/images/redoc-dark-mode.gif&quot;&gt;
&lt;/video&gt;
&lt;p&gt;Read more in the docs - &lt;a href=&quot;https://redocusaurus.vercel.app/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;https://redocusaurus.vercel.app/&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;API Icon by mikicon from the Noun Project&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded></item><item><title><![CDATA[The Hidden Cost of AWS ELBs]]></title><description><![CDATA[This past month we had a really high AWS bill, and it was supposed to be high but not THIS high. The culprit - "Data Transfer Out". I'm sure…]]></description><link>https://rohit.page/blog/posts/hidden-cost-of-aws-elb/</link><guid isPermaLink="false">https://rohit.page/blog/posts/hidden-cost-of-aws-elb/</guid><pubDate>Tue, 01 Sep 2020 13:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This past month we had a really high AWS bill, and it was supposed to be high but not THIS high. The culprit - &quot;Data Transfer Out&quot;. I&apos;m sure I&apos;m not the only one who has had to deal with this.&lt;/p&gt;
&lt;h2&gt;Some Context&lt;/h2&gt;
&lt;p&gt;We had a NodeJS service running on EC2 in an Auto Scaling Group (ASG) behind an Application Load Balancer (ALB) that was supposed to handle a huge volume of requests. We were expecting a consistent increase in traffic this month, so we had made estimates on how it would affect our costs. We had expected some high data transfer out of our Application Load Balancer (ALB) but this was a consistent 4-5x of our estimates. Time to investigate.&lt;/p&gt;
&lt;h2&gt;Analysing the Data&lt;/h2&gt;
&lt;p&gt;After filtering Cost Explorer data with tags, it was clear that the ALB was the source of these costs. I immediately went to CloudWatch and checked the &quot;Processed Bytes&quot; metric of the ALB.&lt;/p&gt;
&lt;h3&gt;Cloud Watch&lt;/h3&gt;
&lt;p&gt;Processed Bytes is supposed to be &lt;em&gt;&quot;the total number of bytes processed by the load balancer over IPv4 and IPv6 ... includes traffic to and from clients&quot;&lt;/em&gt;, so ideally Processed Bytes should be greater than the &quot;Data Transfer Out&quot;. But it was not..?&lt;/p&gt;
&lt;p&gt;Our stats showed Processed Bytes to be around what our estimates had predicted for this amount of traffic. But Cost Explorer was showing a number that was 4-5 times that. Time to contact support.&lt;/p&gt;
&lt;h3&gt;AWS Support&lt;/h3&gt;
&lt;p&gt;We raised a support request to AWS, but we didn&apos;t have high hopes because we were not on the premium support plan. But it was worth a shot.&lt;/p&gt;
&lt;h3&gt;ELB Access Logs&lt;/h3&gt;
&lt;p&gt;Next up, we enabled ALB Access Logs to an S3 bucket and let it generate some logs for some time. Upon checking the logs, we were certain that this wasn&apos;t a problem at the application level. The access logs showed response size consistent with what we had estimated, no inflation due to extra headers or something like that.&lt;/p&gt;
&lt;h3&gt;VPC Flow Logs&lt;/h3&gt;
&lt;p&gt;We wondered if there was some other service running that might be communicating over an Elastic IP or public IP instead of a private IP. To check that, we enabled VPC Flow logs to another S3 bucket and let it generate some logs for some time.&lt;/p&gt;
&lt;p&gt;We didn&apos;t find any services that might be communicating over Elastic IP but we did find some services that could be moved to the same Availability Zone (AZ) and reduced some Regional Data Transfer costs.&lt;/p&gt;
&lt;h2&gt;Frantic Googling&lt;/h2&gt;
&lt;p&gt;After eliminating all possible sources of Data Transfer we still had no clue what was the source of the Cost other than that it was from the ALB. This led to frantic googling and sending/explaining the problem statement to various people.&lt;/p&gt;
&lt;p&gt;Then over the weekend, trying to think of keywords that would not lead me down to more documentation, I finally found this &lt;a href=&quot;https://stackoverflow.com/a/55259794&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;StackOverflow answer&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Q. AWS ELB: How can I see how many bytes of outbound network traffic an SSL-enabled ELB generates&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;A. The handshake of a TLS connection is pretty much constant and do not depend on your application. So your function is really : number of connections * ( size of http responses + constant)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And it finally clicked, the missing piece of the puzzle. Since HTTPS was handled directly by the ALB, it wouldn&apos;t show in the Access Logs as the instance didn&apos;t have to deal with it.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://netsekure.org/2010/03/tls-overhead/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Here&lt;/a&gt; is an article that estimates the payload to average ~6.5k (depending on your certificate size). Our response sizes were really small, and this could be a major part of the response size if the user was visiting the site for the first time. Since a lot of the traffic was new, it would be a significant chunk.&lt;/p&gt;
&lt;p&gt;That led to even more Googling on how HTTPS might affect AWS ELB costs and we came upon &lt;a href=&quot;https://gameanalytics.com/blog/reduce-costs-https-api-aws.html&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;this very good article&lt;/a&gt; that also suggested increasing the default timeout for idle connections to 10 minutes for the ALB along with changing the certificate.&lt;/p&gt;
&lt;h2&gt;Testing Different HTTPS Certs&lt;/h2&gt;
&lt;p&gt;We were currently using the default certificate generated by Amazon Certificate Manager (ACM) with the Load Balancer. We tested the size of the TLS handshake with &lt;a href=&quot;https://www.wormly.com/test_ssl&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Wormly Test SSL Tool&lt;/a&gt; and then replaced it with one generated with Let&apos;s Encrypt. To be thorough we also tested TLS handshake sizes of different websites with different Certificate Authorities.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Certificate Issuer&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Certificate Type&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;SSL Handshake Size (Bytes)&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ACM&lt;/td&gt;
&lt;td&gt;Wildcard (RSA 2048 bits)&lt;/td&gt;
&lt;td&gt;5971&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Let&apos;s Encrypt&lt;/td&gt;
&lt;td&gt;Single (RSA 2048 bits)&lt;/td&gt;
&lt;td&gt;3753&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Let&apos;s Encrypt&lt;/td&gt;
&lt;td&gt;Wildcard (RSA 2048 bits)&lt;/td&gt;
&lt;td&gt;3702&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Let&apos;s Encrypt&lt;/td&gt;
&lt;td&gt;Wildcard (ECC 256 bits)&lt;/td&gt;
&lt;td&gt;3323&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DigiCert&lt;/td&gt;
&lt;td&gt;Single (ECC 256 bits)&lt;/td&gt;
&lt;td&gt;3311&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sectigo&lt;/td&gt;
&lt;td&gt;Wildcard (RSA 2048 bits)&lt;/td&gt;
&lt;td&gt;6720&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;DigiCert was the best with respect to size, but Let&apos;s Encrypt was close enough (&amp;#x26; free!). ECC certificates are not compatible with some older browsers, so we generated both the smaller ECC and RSA certificates through &lt;a href=&quot;https://github.com/acmesh-official/acme.sh/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;acme.sh&lt;/a&gt; and uploaded them through AWS CLI. The ALB will automatically select the best one depending on the client (read more &lt;a href=&quot;https://aws.amazon.com/blogs/aws/new-application-load-balancer-sni/&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;here&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;After adding them to the ALB listener and letting Cost Explorer catch up for a day, we saw a significant decrease in our Data Transfer Out Costs for the same number of requests.&lt;/p&gt;
&lt;p&gt;It was still not equal to the estimates we had, but it will never be because we never considered the TLS handshake size in our estimates. AWS Costs really are a mystery until you actually get the bill.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[CI/CD for your Resume!]]></title><description><![CDATA[After ending up with countless PDFs of my resume and never being sure of which one is the final version. I decided to start versioning it…]]></description><link>https://rohit.page/blog/projects/ci-cd-for-your-resume-wth-this-github-template/</link><guid isPermaLink="false">https://rohit.page/blog/projects/ci-cd-for-your-resume-wth-this-github-template/</guid><pubDate>Sat, 29 Aug 2020 18:30:00 GMT</pubDate><content:encoded>&lt;p&gt;After ending up with countless PDFs of my resume and never being sure of which one is the final version. I decided to start versioning it with the tool I already knew, Git. After finding a good enough LaTeX template, a &lt;code&gt;git init&lt;/code&gt;, and some commits later, I had a good enough resume template with a working Github Action that would publish the generated PDF as a release artifact on every tag. I also added generated PDFs as build artifacts for every commit to preview changes without having access to a working LaTeX env, so that one may be able to make edits from their phone.&lt;/p&gt;
&lt;h3&gt;My Workflow&lt;/h3&gt;
&lt;p&gt;I simply edit the &lt;code&gt;tex&lt;/code&gt; file using the Web Editor and commit my changes. The Github workflow will build and generate a pdf that I can preview using the build artifact.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/resume-preview-workflow.png&quot; alt=&quot;preview-workflow&quot;&gt;&lt;/p&gt;
&lt;p&gt;Once I&apos;m satisfied with the edits, I will create a new release, this will trigger the &quot;Release&quot; pipeline. It will build and publish the pdf as a release artifact.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/release.png&quot; alt=&quot;release-workflow&quot;&gt;&lt;/p&gt;
&lt;p&gt;The release allows me to always have access to the latest version of my resume at the &lt;code&gt;/releases/latest/download/Preview.pdf&lt;/code&gt; path of my repository.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/rohit-gohri/resume-template/releases/latest/download/Preview.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Download latest version&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;More than that, I can preview it directly in my browser using Google Docs by using &lt;code&gt;https://docs.google.com/viewer?url=https://github.com/&amp;#x3C;username&gt;/&amp;#x3C;repo&gt;/releases/latest/download/Preview.pdf&lt;/code&gt;. I can create a short url of this that I can give to anyone knowing it&apos;ll always point to the latest resume without having to worry about updating it.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.google.com/viewer?url=https://github.com/rohit-gohri/resume-template/releases/latest/download/Preview.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Preview latest version&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Motivation&lt;/h3&gt;
&lt;p&gt;When I first realized I could basically code my resume, it finally stopped becoming something I dreaded doing and became something I actively wanted to improve. Adding CI/CD to it was the next natural step.&lt;/p&gt;
&lt;h3&gt;Link to Code&lt;/h3&gt;
&lt;p&gt;This is a Github Template Project with a LaTeX resume template and Github Workflows. You may use this template or add your own LaTeX files to build those.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/rohit-gohri/resume-template&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;resume-template&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Additional Resources&lt;/h3&gt;
&lt;p&gt;My Resume that is using a similar workflow, but with some added integrations to sync my blog with the latest version of my resume:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/rohit-gohri/resume&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;resume&lt;/a&gt; &lt;span class=&quot;no-border&quot;&gt; &lt;a href=&quot;https://github.com/rohit-gohri/resume&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;img src=&quot;https://img.shields.io/github/stars/rohit-gohri/resume?style=social&quot; alt=&quot;github badge&quot;&gt;&lt;/a&gt; &lt;/span&gt;&lt;/p&gt;
&lt;br /&gt;</content:encoded></item><item><title><![CDATA[How to get ESlint and Prettier to play nice in VS Code?]]></title><description><![CDATA[Do you have your project setup with both Prettier and Eslint? Do you want them to run in a specific order instead of using ESLint to run…]]></description><link>https://rohit.page/blog/posts/how-to-get-prettier-eslint-play-nicely-with-vscode/</link><guid isPermaLink="false">https://rohit.page/blog/posts/how-to-get-prettier-eslint-play-nicely-with-vscode/</guid><pubDate>Tue, 28 Apr 2020 18:30:00 GMT</pubDate><content:encoded>&lt;p&gt;Do you have your project setup with both Prettier and Eslint? Do you want them to run in a specific order instead of &lt;a href=&quot;https://prettier.io/docs/en/integrating-with-linters.html#use-eslint-to-run-prettier&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;using ESLint to run prettier?&lt;/a&gt; Here&apos;s how to set your own order in VS Code with this extension:&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=rohit-gohri.format-code-action&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Visual Studio Code Market Place: Format Code Action&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://img.shields.io/visual-studio-marketplace/v/rohit-gohri.format-code-action&quot; alt=&quot;Version&quot;&gt;   &lt;img src=&quot;https://img.shields.io/visual-studio-marketplace/r/rohit-gohri.format-code-action&quot; alt=&quot;Ratings&quot;&gt;&lt;/p&gt;
&lt;p&gt;This extension will register a new &lt;a href=&quot;https://code.visualstudio.com/updates/v1_23#_run-code-actions-on-save&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&quot;codeAction&quot;&lt;/a&gt; which will run &apos;Format Document&apos; using the default formatter (prettier in our case) in VS Code.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;source.formatDocument
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using this and the &lt;code&gt;editor.codeActionsOnSave&lt;/code&gt; setting in VS Code you can specify the order in which to run the eslint codeAction, &lt;code&gt;source.fixAll.eslint&lt;/code&gt;, and format document.&lt;/p&gt;
&lt;h2&gt;Install&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Requires: VS Code 1.44+&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Install through VS Code extensions:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=rohit-gohri.format-code-action&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Visual Studio Code Market Place: Format Code Action&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Can also be installed in VS Code: Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ext install rohit-gohri.format-code-action
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Usage&lt;/h2&gt;
&lt;p&gt;You will need both the &lt;a href=&quot;https://github.com/prettier/prettier-vscode&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;prettier-vscode&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://github.com/microsoft/vscode-eslint&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;vscode-eslint&lt;/code&gt;&lt;/a&gt; extensions installed for this config to work.
Disbale &lt;code&gt;formatOnSave&lt;/code&gt; and use the &lt;code&gt;source.formatDocument&lt;/code&gt; codeAction in whatever order you want with VS Code settings:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;  &quot;editor.formatOnSave&quot;: false,
  &quot;editor.defaultFormatter&quot;: &quot;esbenp.prettier-vscode&quot;,
  &quot;editor.codeActionsOnSave&quot;: [
    &quot;source.formatDocument&quot;,
    &quot;source.fixAll.eslint&quot;
  ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This runs &apos;Format Document&apos; with the default formatter (in this case prettier) and then &lt;code&gt;eslint --fix&lt;/code&gt; for all document types.&lt;/p&gt;
&lt;h3&gt;Or for a specific lanuage only&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;  &quot;editor.formatOnSave&quot;: true,
  &quot;editor.defaultFormatter&quot;: &quot;esbenp.prettier-vscode&quot;,
  &quot;[javascript]&quot;: {
    &quot;editor.formatOnSave&quot;: false,
    &quot;editor.codeActionsOnSave&quot;: [
        &quot;source.formatDocument&quot;,
        &quot;source.fixAll.eslint&quot;
    ]
  },
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This would run prettier by default, but for javascript files would run prettier and then eslint. If you want to reverse the order then just reverse the array.&lt;/p&gt;
&lt;h2&gt;Motivation&lt;/h2&gt;
&lt;p&gt;I wanted to use prettier and eslint together, to override some of prettier&apos;s mpre opinionated style rules with a eslint config (particularly &lt;a href=&quot;https://github.com/prettier/eslint-config-prettier/issues/26&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;brace-style&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Instead of using workarounds like &lt;a href=&quot;https://github.com/prettier/prettier-eslint&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;prettier-eslint&lt;/code&gt;&lt;/a&gt; which don&apos;t work for all eslint configurations as it hasn&apos;t been updated to work with &lt;a href=&quot;https://github.com/prettier/prettier-eslint/issues/222&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;eslint v6 properly&lt;/a&gt;. We can simply run prettier and eslint one by one like one would do on the command line. Here is how that looks like in my &lt;code&gt;package.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;  &quot;scripts&quot;: {
    &quot;format&quot;: &quot;npm run prettier:fix &amp;#x26;&amp;#x26; npm run lint:fix&quot;,
    &quot;lint&quot;: &quot;eslint src --ext=js&quot;,
    &quot;lint:fix&quot;: &quot;npm run lint -- --fix&quot;,
    &quot;prettier&quot;: &quot;prettier -c&quot;,
    &quot;prettier:fix&quot;: &quot;npm run prettier -- --write&quot;
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;npm run format&lt;/code&gt; command allows me to fix some of the prettier formatting with eslint by running them both in an order. I wanted to replicate this same behavior with my editor&apos;s &quot;Format on Save&quot; functionality.&lt;/p&gt;
&lt;p&gt;VS Code only allows setting one default formatter. So I could either run Prettier or run ESLint on save. That was until it introduced &lt;a href=&quot;https://code.visualstudio.com/updates/v1_23#_run-code-actions-on-save&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&quot;codeActionsOnSave&quot;&lt;/a&gt;. This separated formatters and &quot;Source&quot; fixers like &lt;code&gt;vscode-eslint&lt;/code&gt;. This is how one would enable both prettier and eslint in VS Code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;&quot;editor.formatOnSave&quot;: true,
&quot;editor.defaultFormatter&quot;: &quot;esbenp.prettier-vscode&quot;,
&quot;editor.codeActionsOnSave&quot;: {
  &quot;source.fixAll.eslint&quot;: true
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But this would often lead to prettier being run after eslint, and eslint errors still being present as this defined no order for formatters and coder actions.&lt;/p&gt;
&lt;p&gt;Since the &lt;a href=&quot;https://code.visualstudio.com/updates/v1_44#_explicit-ordering-for-code-actions-on-save&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;March 2020 (v1.44)&lt;/a&gt; update, VS Code allows setting &lt;code&gt;codeActionsOnSave&lt;/code&gt; as an array. This allows setting the order of the code actions.&lt;/p&gt;
&lt;p&gt;This extension uses the &lt;a href=&quot;https://code.visualstudio.com/api/references/vscode-api#CodeActionProvider&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;&lt;code&gt;CodeActionProvider&lt;/code&gt;&lt;/a&gt; api to implement a simple code action that runs &apos;Format Document&apos; on the current file. This allows us to disable &lt;code&gt;formatOnSave&lt;/code&gt; and use it as a codeAction instead in a specific order with other extensions.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[DangerJS Plugin - TODOs]]></title><description><![CDATA[What is It? A plugin for danger-js that collects all the "TODO" comments introduced in a pull request (PR) and posts them as a comment on…]]></description><link>https://rohit.page/blog/projects/dangerjs-plugin-todos/</link><guid isPermaLink="false">https://rohit.page/blog/projects/dangerjs-plugin-todos/</guid><pubDate>Mon, 16 Mar 2020 18:30:00 GMT</pubDate><content:encoded>&lt;h3&gt;What is It?&lt;/h3&gt;
&lt;p&gt;A plugin for &lt;a href=&quot;https://danger.systems/js&quot; title=&quot;Danger JS Docs&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;danger-js&lt;/a&gt; that collects all the &quot;TODO&quot; comments introduced in a pull request (PR) and posts them as a comment on the PR.&lt;/p&gt;
&lt;p&gt;It&apos;s a pretty simple plugin that just scans your diff for matching comments, it supports different language style comments including JS, C, C++, Ruby, Java, Elm, HTML, Bash, etc. (&lt;a href=&quot;https://github.com/rohit-gohri/danger-plugin-todos/issues&quot; title=&quot;danger-plugin-todos&amp;#x27; issues on Github&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;open an issue&lt;/a&gt; if something is missing). You can also specify custom keywords other than &quot;TODO&quot; or &quot;FIXME&quot;.&lt;/p&gt;
&lt;h3&gt;How to set it up?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Set up &lt;a href=&quot;https://danger.systems/js&quot; title=&quot;Danger JS Docs&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;danger-js&lt;/a&gt; and add it to your continuous integration (CI) pipeline&lt;/li&gt;
&lt;li&gt;Add &lt;a href=&quot;https://www.npmjs.com/package/danger-plugin-todos&quot; title=&quot;danger-plugin-todos on npm&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;danger-plugin-todos&lt;/a&gt; to the &quot;dangerfile&quot; as mentioned in the README&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Next time your CI pipeline runs for any PR this plugin will scan the diff to get all the To-Do comments and post them through danger as a comment on the PR. The comment will update on consecutive runs.&lt;/p&gt;
&lt;h3&gt;Possible Workflows&lt;/h3&gt;
&lt;h5&gt;Scenario A&lt;/h5&gt;
&lt;p&gt;Sometimes when you start work on a feature you see things along the way that need to be fixed and you end up adding a To-Do or Fix-Me comment for them. The feature branch then gets merged and the comments quickly gets forgotten if they were not related to a part of the code base that sees frequent updates.&lt;/p&gt;
&lt;p&gt;To track such To-Dos this plugin can be used, as a list of them will be added as a comment to the PR. This list of To-Dos can then be moved to a new issue before the PR is merged and the issue added to the backlog.&lt;/p&gt;
&lt;h5&gt;Scenario B&lt;/h5&gt;
&lt;p&gt;This plugin can also be used to create an actual auto-updating To-Do List.&lt;/p&gt;
&lt;p&gt;Just add all your tasks for the PR in the code as To-Dos and open a Work In Progress (WIP) PR, this will create a list of To-Dos in the PR comments.&lt;/p&gt;
&lt;p&gt;Now, as you work on them and remove them the comment will be updated and your list will become smaller. Once you are done with them the PR will be ready to merge.&lt;/p&gt;
&lt;h3&gt;More Features Incoming&lt;/h3&gt;
&lt;p&gt;There are some new ideas that are being worked on (&lt;a href=&quot;https://github.com/rohit-gohri/danger-plugin-todos/issues&quot; title=&quot;danger-plugin-todos&amp;#x27; open issues&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;check the issues here&lt;/a&gt;), like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Generate a link to create a new issue with a list of To-Dos automatically&lt;/strong&gt;: This would be pretty great for our Scenario A above. Link can be customized to support different issue trackers&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Show completed To-Dos&lt;/strong&gt;: PRs that complete and remove old To-Dos should be celebrated. This would add a success message on removing old To-Dos.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Anyways, I hope this is useful for you. If you have any ideas on how to make this better feel free to message me on &lt;a href=&quot;https://twitter.com/rohit_gohri&quot; title=&quot;Twitter Profile&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Twitter&lt;/a&gt; or open an issue on the repo.&lt;/p&gt;</content:encoded></item></channel></rss>