<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: insidewhy</title>
    <description>The latest articles on Forem by insidewhy (@insidewhy).</description>
    <link>https://forem.com/insidewhy</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%2F369163%2F83a984aa-84c8-4a9d-b302-0890ceb067db.png</url>
      <title>Forem: insidewhy</title>
      <link>https://forem.com/insidewhy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/insidewhy"/>
    <language>en</language>
    <item>
      <title>Using ast-grep with a vue project</title>
      <dc:creator>insidewhy</dc:creator>
      <pubDate>Wed, 01 Jan 2025 04:10:58 +0000</pubDate>
      <link>https://forem.com/insidewhy/using-ast-grep-with-a-vue-project-2np2</link>
      <guid>https://forem.com/insidewhy/using-ast-grep-with-a-vue-project-2np2</guid>
      <description>&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;I've create a node package that supplies the necessary config to support vue/scss for ast-grep (with binaries built for multiple systems and architectures) via &lt;a href="https://github.com/insidewhy/pusang-ina" rel="noopener noreferrer"&gt;pusang-ina&lt;/a&gt;. The following article explains how to configure this manually.&lt;/p&gt;

&lt;p&gt;The amazing &lt;a href="https://ast-grep.github.io/" rel="noopener noreferrer"&gt;ast-grep&lt;/a&gt; tool does not support &lt;code&gt;vue&lt;/code&gt; and &lt;code&gt;scss&lt;/code&gt; by default but can be configured to do so.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ast-grep&lt;/code&gt; is like a much more powerful version of &lt;code&gt;ripgrep&lt;/code&gt; or &lt;code&gt;grep&lt;/code&gt; when it comes to searching for patterns within code as it understands the structure of the code (via the library &lt;a href="https://tree-sitter.github.io/tree-sitter/" rel="noopener noreferrer"&gt;tree-sitter&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;For example the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ast-grep &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;h2&amp;gt;$A&amp;lt;/h2&amp;gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Will show all &lt;code&gt;&amp;lt;h2&amp;gt;&lt;/code&gt; tags in your &lt;code&gt;html&lt;/code&gt; (and also &lt;code&gt;vue&lt;/code&gt; files after applying the config from this post). Then the following could change all &lt;code&gt;&amp;lt;h2&amp;gt;&lt;/code&gt; tags to &lt;code&gt;&amp;lt;h3&amp;gt;&lt;/code&gt; tags interactively (each change will be shown and then &lt;code&gt;y&lt;/code&gt; can be used to accept it or &lt;code&gt;n&lt;/code&gt; to reject it):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ast-grep &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;h2&amp;gt;$A&amp;lt;/h2&amp;gt;'&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;h3&amp;gt;$A&amp;lt;/h3&amp;gt;'&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;-U&lt;/code&gt; can be used instead of &lt;code&gt;-i&lt;/code&gt; to apply the changes without asking or neither can be used to view all the changes as a patch.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ast-grep&lt;/code&gt; also has some overlap with &lt;code&gt;eslint&lt;/code&gt;, rules can be stored within the project along with fixes and these can be tested/fixed using &lt;code&gt;ast-grep scan&lt;/code&gt; and &lt;code&gt;ast-grep scan -U&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Simple Configuration
&lt;/h2&gt;

&lt;p&gt;The steps in the next section can be used to setup ast-grep for vue manually or the following can be run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm add &lt;span class="nt"&gt;-D&lt;/span&gt; pusang-ina
&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-sf&lt;/span&gt; node_modules/pusang-ina/sgconfig.yml &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Manual Configuration
&lt;/h2&gt;

&lt;p&gt;All of the following commands should be run from the root directory of your &lt;code&gt;vue&lt;/code&gt; project.&lt;/p&gt;

&lt;p&gt;First create a "rules" directory and add it to &lt;code&gt;git&lt;/code&gt;. This directory must exist for the config to be accepted so for now just create an empty directory. Rules may also use utilities so that common config can be shared across rules, so we're going to create both directories with this config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;ast/&lt;span class="o"&gt;{&lt;/span&gt;rules,utils&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;touch &lt;/span&gt;ast/&lt;span class="o"&gt;{&lt;/span&gt;rules,utils&lt;span class="o"&gt;}&lt;/span&gt;/.keep-dir
git add ast/&lt;span class="o"&gt;{&lt;/span&gt;rules,utils&lt;span class="o"&gt;}&lt;/span&gt;/.keep-dir
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then create a file &lt;code&gt;sgconfig.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;ruleDirs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ast/rules&lt;/span&gt;
&lt;span class="na"&gt;utilDirs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ast/utils&lt;/span&gt;

&lt;span class="na"&gt;customLanguages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;vue&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;libraryPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.tree-sitter/vue.so&lt;/span&gt;
    &lt;span class="na"&gt;extensions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;vue&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;expandoChar&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$&lt;/span&gt;
  &lt;span class="na"&gt;scss&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;libraryPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.tree-sitter/scss.so&lt;/span&gt;
    &lt;span class="na"&gt;extensions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;scss&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;expandoChar&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$&lt;/span&gt;

&lt;span class="na"&gt;languageInjections&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;hostLanguage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vue&lt;/span&gt;
    &lt;span class="na"&gt;rule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;template&amp;gt;$CONTENT&amp;lt;/template&amp;gt;&lt;/span&gt;
    &lt;span class="na"&gt;injected&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;html&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;hostLanguage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vue&lt;/span&gt;
    &lt;span class="na"&gt;rule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;script $$$ lang="ts" $$$&amp;gt;$CONTENT&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="na"&gt;injected&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;typescript&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;hostLanguage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vue&lt;/span&gt;
    &lt;span class="na"&gt;rule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;style $$$ lang="scss" $$$&amp;gt;$CONTENT&amp;lt;/style&amp;gt;&lt;/span&gt;
    &lt;span class="na"&gt;injected&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scss&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now a script is needed to create the libraries that this &lt;code&gt;ast-grep&lt;/code&gt; configuration needs to understand &lt;code&gt;vue&lt;/code&gt; and &lt;code&gt;scss&lt;/code&gt; files. This could be created at &lt;code&gt;scripts/init-ast-grep-config.sh&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class="nv"&gt;outdir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;/.tree-sitter/
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nv"&gt;$outdir&lt;/span&gt;

&lt;span class="nb"&gt;cd&lt;/span&gt; /tmp
git clone https://github.com/tree-sitter-grammars/tree-sitter-vue
&lt;span class="nb"&gt;cd &lt;/span&gt;tree-sitter-vue
pnpm dlx tree-sitter-cli build
&lt;span class="nb"&gt;cp &lt;/span&gt;vue.so &lt;span class="nv"&gt;$outdir&lt;/span&gt;/
&lt;span class="nb"&gt;cd&lt;/span&gt; ..
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; tree-sitter-vue

git clone https://github.com/serenadeai/tree-sitter-scss
&lt;span class="nb"&gt;cd &lt;/span&gt;tree-sitter-scss
pnpm dlx tree-sitter-cli build
&lt;span class="nb"&gt;cp &lt;/span&gt;scss.so &lt;span class="nv"&gt;$outdir&lt;/span&gt;/
&lt;span class="nb"&gt;cd&lt;/span&gt; ..
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; tree-sitter-scss
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then ensure this script is executable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;a+x scripts/init-ast-grep-config.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script stores files in the directory &lt;code&gt;.tree-sitter&lt;/code&gt; within the project, these libraries are binaries so they are unique to the platform on which they were compiled/installed so it's a good idea to add the &lt;code&gt;.tree-sitter&lt;/code&gt; directory to &lt;code&gt;.gitignore&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next add a reference to this script to the &lt;code&gt;scripts&lt;/code&gt; section of &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"init-ast-grep-config"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./scripts/init-ast-grep-config.sh"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now document this in your &lt;code&gt;readme.md&lt;/code&gt;, commit everything to &lt;code&gt;git&lt;/code&gt;, and developers on the project can begin using &lt;code&gt;ast-grep&lt;/code&gt; on your &lt;code&gt;vue&lt;/code&gt; project after running &lt;code&gt;pnpm run init-ast-grep-config&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add some rules
&lt;/h2&gt;

&lt;p&gt;Now that &lt;code&gt;ast-grep&lt;/code&gt; is configured I'll show how easy it is to create a rule to enforce code practices. &lt;code&gt;vue&lt;/code&gt; allows &lt;code&gt;defineEmits&lt;/code&gt; to be written in a type safe way:&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="nx"&gt;defineEmits&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;update:value&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="kr"&gt;string&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or an unsafe way:&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="nf"&gt;defineEmits&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;update:value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If using typescript then there's not much reason to use the type unsafe version and this can be enforced with a five line rule in the project (to do the same with &lt;code&gt;eslint&lt;/code&gt; would involve a lot more work). Create the following &lt;code&gt;yaml&lt;/code&gt; file at &lt;code&gt;ast/rules/type-unsafe-define-emits.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;type-unsafe-define-emits&lt;/span&gt;
&lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;typescript&lt;/span&gt;
&lt;span class="na"&gt;rule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;defineEmits([$$$])&lt;/span&gt;
&lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Use type safe version of defineEmits&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run &lt;code&gt;ast-grep scan&lt;/code&gt; and it will raise an error is the type unsafe version of &lt;code&gt;defineEmits&lt;/code&gt; has been used.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Suggested Jira setup for software engineering projects</title>
      <dc:creator>insidewhy</dc:creator>
      <pubDate>Thu, 21 Nov 2024 06:30:29 +0000</pubDate>
      <link>https://forem.com/insidewhy/suggested-jira-setup-for-software-engineering-projects-11hp</link>
      <guid>https://forem.com/insidewhy/suggested-jira-setup-for-software-engineering-projects-11hp</guid>
      <description>&lt;p&gt;I've been refining the way I deal with software development workflows for more than 20 years now. I've worked in different positions of seniority, with team/organisation sizes ranging from minuscule to huge, and at various stages of the software development life cycle. This article represents the current state of evolution of the Jira configuration I use.&lt;/p&gt;

&lt;h1&gt;
  
  
  Team-managed vs company-managed projects
&lt;/h1&gt;

&lt;p&gt;I advise choosing &lt;code&gt;company-managed&lt;/code&gt;, &lt;code&gt;team-managed&lt;/code&gt; projects offer much less customisability, in particular the scrum/kanban board layout is extremely limited and cannot be customised to the degree shown in the configuration suggested by this article.&lt;/p&gt;

&lt;h1&gt;
  
  
  Custom fields
&lt;/h1&gt;

&lt;p&gt;The following fields are not part of Jira by default but can be useful for tracking important information. These fields don't create any additional maintenance burden as they are updated automatically by the workflow suggestion shown later in the article.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Developer&lt;/code&gt;: The Jira user associated with the person who developed the work item.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Tester&lt;/code&gt;: The Jira user associated with the person who tested the work item.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Start Time&lt;/code&gt;: The date and time the work item started being actively worked on (post refinement).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Development Complete Time&lt;/code&gt;: The date and time the work item was transitioned from &lt;code&gt;In Review&lt;/code&gt; to &lt;code&gt;Ready for QA&lt;/code&gt; or &lt;code&gt;Done&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;End Time&lt;/code&gt;: The date and time the work item was closed. There is already the &lt;code&gt;resolution&lt;/code&gt; field which includes the &lt;code&gt;resolutiondate&lt;/code&gt; component, but for some reason Jira does not show the resolution date anywhere in the UI and it can be very useful to see.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Workflow
&lt;/h1&gt;

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

&lt;h2&gt;
  
  
  Statuses
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Draft
&lt;/h3&gt;

&lt;p&gt;The initial state of any work item to show it hasn't been refined. After a refinement session:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Discard&lt;/code&gt; updates &lt;code&gt;Status&lt;/code&gt; to &lt;code&gt;Done&lt;/code&gt; and:

&lt;ul&gt;
&lt;li&gt;Updates &lt;code&gt;Resolution&lt;/code&gt; to &lt;code&gt;Won't Do&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Updates &lt;code&gt;Story Points&lt;/code&gt; to &lt;code&gt;0&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;Mark as duplicate&lt;/code&gt; updates &lt;code&gt;Status&lt;/code&gt; to &lt;code&gt;Done&lt;/code&gt; and:

&lt;ul&gt;
&lt;li&gt;Updates &lt;code&gt;Resolution&lt;/code&gt; to &lt;code&gt;Duplicate&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Updates &lt;code&gt;Story Points&lt;/code&gt; to &lt;code&gt;0&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;Finish refinement&lt;/code&gt; updates &lt;code&gt;Status&lt;/code&gt; to &lt;code&gt;To do&lt;/code&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;Finish refinement but blocked&lt;/code&gt; updates &lt;code&gt;Status&lt;/code&gt; to &lt;code&gt;Blocked&lt;/code&gt;
&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Blocked
&lt;/h3&gt;

&lt;p&gt;A work item that has been refined but is blocked due to other pending work items.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Unblock&lt;/code&gt; updates &lt;code&gt;Status&lt;/code&gt; to &lt;code&gt;To do&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Redraft&lt;/code&gt; updates &lt;code&gt;Status&lt;/code&gt; to &lt;code&gt;Draft&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  To do
&lt;/h3&gt;

&lt;p&gt;The work item is ready to work on unless there is are work items related to it with an &lt;code&gt;is blocked by&lt;/code&gt; association that are not yet &lt;code&gt;Done&lt;/code&gt;. Generally issues with a status of &lt;code&gt;To do&lt;/code&gt; won't have an &lt;code&gt;Assignee&lt;/code&gt; but a person can set this if they want to &lt;em&gt;claim&lt;/em&gt; the work item to show that they would like to be the one to develop it in the future.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Redraft&lt;/code&gt; reverts &lt;code&gt;Status&lt;/code&gt; to &lt;code&gt;Draft&lt;/code&gt; in case something was missed or life changed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Start&lt;/code&gt; updates &lt;code&gt;Status&lt;/code&gt; to &lt;code&gt;In Progress&lt;/code&gt; and:

&lt;ul&gt;
&lt;li&gt;Updates &lt;code&gt;Assignee&lt;/code&gt; to the user who performed the transition&lt;/li&gt;
&lt;li&gt;Copies &lt;code&gt;Assignee&lt;/code&gt; over &lt;code&gt;Developer&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Updates &lt;code&gt;Start time&lt;/code&gt; to the current date and time&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  In Progress
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Pause&lt;/code&gt; updates &lt;code&gt;Status&lt;/code&gt; to &lt;code&gt;To do&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Request review&lt;/code&gt; updates &lt;code&gt;Status&lt;/code&gt; to &lt;code&gt;In Review&lt;/code&gt; and:

&lt;ul&gt;
&lt;li&gt;Updates &lt;code&gt;Ready For Review Time&lt;/code&gt; to the current date and time&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;Mark invalid&lt;/code&gt; updates &lt;code&gt;Status&lt;/code&gt; to &lt;code&gt;Done&lt;/code&gt; and:

&lt;ul&gt;
&lt;li&gt;Updates &lt;code&gt;Resolution&lt;/code&gt; to &lt;code&gt;Won't Do&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Clears &lt;code&gt;Ready for Review Time&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Clears &lt;code&gt;Start Time&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Clears &lt;code&gt;Development Complete Time&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Clears &lt;code&gt;End Time&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Updates &lt;code&gt;Story Points&lt;/code&gt; to &lt;code&gt;0&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  In Review
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Merge code and skip testing&lt;/code&gt; can be useful for updating &lt;code&gt;Status&lt;/code&gt; to &lt;code&gt;Done&lt;/code&gt; when it doesn't require any testing. This is usually not a good idea but an escape hatch when life doesn't make sense. This transition also:

&lt;ul&gt;
&lt;li&gt;Updates &lt;code&gt;Resolution&lt;/code&gt; to &lt;code&gt;Done&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Copies &lt;code&gt;Developer&lt;/code&gt; over &lt;code&gt;Tester&lt;/code&gt; to indicate that the developer tested their own work item (which is generally bad practice but can be useful in limited circumstances)&lt;/li&gt;
&lt;li&gt;Updates &lt;code&gt;End Time&lt;/code&gt; to the current date and time&lt;/li&gt;
&lt;li&gt;Updates &lt;code&gt;Development Complete Time&lt;/code&gt; to the current date and time&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;Merge code&lt;/code&gt; updates &lt;code&gt;Status&lt;/code&gt; to &lt;code&gt;Ready for review&lt;/code&gt; and:

&lt;ul&gt;
&lt;li&gt;Clears &lt;code&gt;Assignee&lt;/code&gt;. The &lt;code&gt;Developer&lt;/code&gt; custom field will still be there for use later&lt;/li&gt;
&lt;li&gt;Updates &lt;code&gt;Development Complete Time&lt;/code&gt; to the current date and time&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Ready for QA
&lt;/h3&gt;

&lt;p&gt;The work item is ready for QA and won't have an &lt;code&gt;Assignee&lt;/code&gt;, but a person may set the &lt;code&gt;Assignee&lt;/code&gt; to &lt;em&gt;claim&lt;/em&gt; the work item to show that they would like to be the one to test the work item in the future. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Start testing&lt;/code&gt; updates &lt;code&gt;Status&lt;/code&gt; to &lt;code&gt;In testing&lt;/code&gt; and:

&lt;ul&gt;
&lt;li&gt;Updates &lt;code&gt;Assignee&lt;/code&gt; to the user who performed the transition.&lt;/li&gt;
&lt;li&gt;Copies &lt;code&gt;Assignee&lt;/code&gt; over &lt;code&gt;Tester&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;Request change&lt;/code&gt; updates &lt;code&gt;Status&lt;/code&gt; to &lt;code&gt;To do&lt;/code&gt; in case something was noticed before testing started, when this happens a comment should be left in the work item indicating what should change. This also:

&lt;ul&gt;
&lt;li&gt;Copies &lt;code&gt;Developer&lt;/code&gt; over &lt;code&gt;Assignee&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  In testing
&lt;/h3&gt;

&lt;p&gt;The work item is actively being tested.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Request change&lt;/code&gt; updates &lt;code&gt;Status&lt;/code&gt; to &lt;code&gt;To do&lt;/code&gt;, if this is done the tester should leave a comment in the work item to say what bugs were found or what changes should be made. It also:

&lt;ul&gt;
&lt;li&gt;Copies &lt;code&gt;Developer&lt;/code&gt; over &lt;code&gt;Assignee&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;Pass testing&lt;/code&gt; updates &lt;code&gt;Status&lt;/code&gt; to &lt;code&gt;Done&lt;/code&gt; and:

&lt;ul&gt;
&lt;li&gt;Updates &lt;code&gt;Resolution&lt;/code&gt; to &lt;code&gt;Done&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Copies &lt;code&gt;Developer&lt;/code&gt; over &lt;code&gt;Assignee&lt;/code&gt;. It's useful to quickly associated a closed work item with the person who developed it and the &lt;code&gt;Tester&lt;/code&gt; custom field is still there for deep diving.&lt;/li&gt;
&lt;li&gt;Updates &lt;code&gt;End time&lt;/code&gt; to the current date and time.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Done
&lt;/h3&gt;

&lt;p&gt;The work item is most likely done with forever but may be reopened in case something comes up at a later date.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Reopen&lt;/code&gt; updates &lt;code&gt;Status&lt;/code&gt; to &lt;code&gt;To Do&lt;/code&gt; and:

&lt;ul&gt;
&lt;li&gt;Clears &lt;code&gt;Assignee&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Clears &lt;code&gt;End time&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Clears &lt;code&gt;Resolution&lt;/code&gt; which also has the effect of clearing the &lt;code&gt;resolutiondate&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Automations
&lt;/h2&gt;

&lt;p&gt;Jira automations can be used to prevent &lt;em&gt;naughty things&lt;/em&gt;:&lt;/p&gt;

&lt;h3&gt;
  
  
  Do not assign QA issues to developer
&lt;/h3&gt;

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

&lt;p&gt;A developer shouldn't test their own code, any attempts to assign a work item in &lt;code&gt;Ready for QA&lt;/code&gt; or &lt;code&gt;In testing&lt;/code&gt; statuses to the user in the &lt;code&gt;Developer&lt;/code&gt; custom field will revert the assignation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prevent Developer transitioning issue to "In Test"
&lt;/h3&gt;

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

&lt;p&gt;A person shouldn't transition an issue to &lt;code&gt;In testing&lt;/code&gt; when they were the one who developed it, any attempts to do this will be reverted. Here the &lt;code&gt;Edit issue fields advanced&lt;/code&gt; option has the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fields"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Tester"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The board
&lt;/h2&gt;

&lt;p&gt;The following filter creates a nice order for the kanban/scrum board:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;project = YOURPROJECT
and (
  status != Done
  or resolution not in (Duplicate, "Cannot Reproduce", "Won't Do")
)
order by End Time desc, fixVersion asc, priority desc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Don't show issues which were discarded.&lt;/li&gt;
&lt;li&gt;Order the work items in the &lt;code&gt;Done&lt;/code&gt; column by the resolution date in descending order (i.e. most recently resolved work items at the top). &lt;code&gt;End Time&lt;/code&gt; won't be set for work items without a status of &lt;code&gt;Done&lt;/code&gt; (as enforced by the workflow) so this &lt;code&gt;order by&lt;/code&gt; clause won't have any effect for work items in other columns.&lt;/li&gt;
&lt;li&gt;For columns other than &lt;code&gt;Done&lt;/code&gt; work items will be shown in order of their &lt;code&gt;fixVersion&lt;/code&gt; (i.e. work items for the upcoming release will be shown at the top of the board), and within each release, work items will be ordered by priority.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Releases
&lt;/h1&gt;

&lt;p&gt;Assigning work items to releases is a great way to keep track of progress of a particular release and for ordering the scrum/kanban board. Note that the releases list should be in descending date order (i.e. the most recent release should be at the top of the list and the oldest release should be at the bottom of the list). Releases can be dragged and dropped to reorder the list.&lt;/p&gt;

&lt;h1&gt;
  
  
  Filters
&lt;/h1&gt;

&lt;p&gt;The following filters can be useful:&lt;/p&gt;

&lt;h2&gt;
  
  
  Open issues sorted by release then priority
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;project = MYPROJECT
and type != Epic and status != Done
order by fixVersion ASC, priority desc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Closed issues ordered by resolution date
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;project = MYPROJECT
and resolution not in (Duplicate, "Cannot Reproduce", "Won't Do")
order by resolutiondate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Issues in progress
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;project = MYPROJCT
and type != Epic
and status in ("In progress",  "In review", "In testing")
order by fixVersion ASC, priority desc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Issues for next release
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;project = MYPROJECT
and fixVersion = earliestUnreleasedVersion()
and (
  status != Done
  or resolution not in (Duplicate, "Cannot Reproduce", "Won't Do")
)
order by resolutiondate desc, fixVersion asc, priority desc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Issues to be tested
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;project = MYPROJECT
and type != Epic and status = "Ready for QA"
order by fixVersion ASC, priority desc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Github
&lt;/h1&gt;

&lt;p&gt;If the project "short code" in Jira is &lt;code&gt;PJ&lt;/code&gt; then issue identifiers will look like &lt;code&gt;PJ-1&lt;/code&gt;, &lt;code&gt;PJ-42&lt;/code&gt; etc. When creating a it's useful to name the branch after the Jira work items with an optional prefix to show the type of work e.g.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;fix/PJ-1&lt;/code&gt;: A fix relating to the issue &lt;code&gt;PJ-1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;chore/PJ-2&lt;/code&gt;: A task relating to the issue &lt;code&gt;PJ-2&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;feature/PJ-3&lt;/code&gt;: A feature/story relating to the issue &lt;code&gt;PJ-3&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PJ-4&lt;/code&gt;: If you don't care to mark the task type in the branch name.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Assuming this convention has been used (or that the PR title begins with &lt;code&gt;PJ-42:&lt;/code&gt;) a github action can be used to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set the &lt;code&gt;pull request&lt;/code&gt; title to the Jira work item title.&lt;/li&gt;
&lt;li&gt;Add a text block to the &lt;code&gt;pull request&lt;/code&gt; description with a link to the Jira work item (this block can be updated when the Jira content changes as updating text blocks is a supported feature of &lt;code&gt;insidewhy/actions-body-fields&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To do this the following file can be created within the github repository at a file location such as &lt;code&gt;.github/workflows/jira-linker.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jira-linker&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;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;opened&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;reopened&lt;/span&gt;

&lt;span class="na"&gt;concurrency&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}&lt;/span&gt;
  &lt;span class="na"&gt;cancel-in-progress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;action-jira-linker&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;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;pull-requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
      &lt;span class="na"&gt;issues&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;determine associated issue id&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;get-ticket-id&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;ticket_match='grep -Eqx [A-Z]+-[0-9]+'&lt;/span&gt;

          &lt;span class="s"&gt;# first try to get the ticket id from the title&lt;/span&gt;
          &lt;span class="s"&gt;title="${{github.event.pull_request.title}}"&lt;/span&gt;
          &lt;span class="s"&gt;ticket_id=${title%%:*}&lt;/span&gt;
          &lt;span class="s"&gt;if ! echo $ticket_id | $ticket_match; then&lt;/span&gt;
            &lt;span class="s"&gt;# otherwise try to get the ticket id from the branch&lt;/span&gt;
            &lt;span class="s"&gt;branch=${{ github.head_ref || github.ref_name }}&lt;/span&gt;
            &lt;span class="s"&gt;ticket_id=${branch#*/}&lt;/span&gt;
            &lt;span class="s"&gt;ticket_id=${ticket_id%%_*}&lt;/span&gt;
            &lt;span class="s"&gt;if ! echo $ticket_id | $ticket_match; then&lt;/span&gt;
              &lt;span class="s"&gt;exit 0&lt;/span&gt;
            &lt;span class="s"&gt;fi&lt;/span&gt;
          &lt;span class="s"&gt;fi&lt;/span&gt;
          &lt;span class="s"&gt;echo "ticket-id=$ticket_id" &amp;gt;&amp;gt; $GITHUB_OUTPUT&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;insidewhy/action-get-jira-issue@v1&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jira&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;steps.get-ticket-id.outputs.ticket-id&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;user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.JIRA_USER }}&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.JIRA_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;base-url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.JIRA_BASE_URL }}&lt;/span&gt;
          &lt;span class="na"&gt;ticket-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.get-ticket-id.outputs.ticket-id }}&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;insidewhy/action-body-fields@v1&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;steps.get-ticket-id.outputs.ticket-id&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;prepend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;steps.get-ticket-id.outputs.ticket-id&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;steps.jira.outputs.summary&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt;
          &lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;##&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Status'&lt;/span&gt;
          &lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;Jira: [${{ steps.get-ticket-id.outputs.ticket-id }}: ${{ steps.jira.outputs.summary }}](${{ steps.jira.outputs.link }})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For this to work a Jira token must be created, then information about this token must be set via github secrets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;JIRA_USER&lt;/code&gt;: The user that created the Jira token.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;JIRA_TOKEN&lt;/code&gt;: The token itself.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;JIRA_BASE_URL&lt;/code&gt;: The URL of the Jira instance e.g. &lt;code&gt;https://my-kitten-project.atlassian.net&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>jira</category>
      <category>workflow</category>
      <category>projectmanagement</category>
    </item>
    <item>
      <title>Installing kde 6 (plasma 6) from source on ubuntu 24.04</title>
      <dc:creator>insidewhy</dc:creator>
      <pubDate>Sat, 20 Apr 2024 20:06:32 +0000</pubDate>
      <link>https://forem.com/insidewhy/installing-kde-6-plasma-6-on-ubuntu-2404-2bll</link>
      <guid>https://forem.com/insidewhy/installing-kde-6-plasma-6-on-ubuntu-2404-2bll</guid>
      <description>&lt;h2&gt;
  
  
  Install qt 6
&lt;/h2&gt;

&lt;p&gt;Install the latest qt 6.6 releases using &lt;a href="https://doc.qt.io/qt-6/qt-online-installation.html"&gt;qt online installer&lt;/a&gt;. Just select qt 6.6, no other packages are needed. I chose to install it to &lt;code&gt;/opt/qt&lt;/code&gt; and created this directory with &lt;code&gt;sudo mkdir /opt/qt &amp;amp;&amp;amp; chown $USER /opt/qt&lt;/code&gt;. Then put something like &lt;code&gt;/opt/qt/&amp;lt;qt-version&amp;gt;/gcc_64/bin&lt;/code&gt; to the head of the &lt;code&gt;PATH&lt;/code&gt; environment variable via &lt;code&gt;~/.zshrc&lt;/code&gt; or &lt;code&gt;~/.bashrc&lt;/code&gt; (for me &lt;code&gt;&amp;lt;qt-version&amp;gt;&lt;/code&gt; was &lt;code&gt;6.6.3&lt;/code&gt;, the latest &lt;code&gt;6.6&lt;/code&gt; release at the time I wrote this).&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup &lt;code&gt;pkg-config&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Ensure &lt;code&gt;pkg-config&lt;/code&gt; can find the qt libraries installed above by adding &lt;code&gt;/opt/qt/&amp;lt;qt-version&amp;gt;/gcc_64/lib/pkgconfig&lt;/code&gt; to &lt;code&gt;PKG_CONFIG_PATH&lt;/code&gt; via your shell startup file. If you use &lt;code&gt;brew&lt;/code&gt; then it's probably installed its own &lt;code&gt;pkg-config&lt;/code&gt; which is now at the head of your &lt;code&gt;$PATH&lt;/code&gt;. In this case ensure it can find the system &lt;code&gt;pkgconfig&lt;/code&gt; files by also adding &lt;code&gt;/usr/lib/x86_64-linux-gnu/pkgconfig:/usr/share/pkgconfig&lt;/code&gt; to &lt;code&gt;PKG_CONFIG_PATH&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Something like this should be okay:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; /home/linuxbrew &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PKG_CONFIG_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/linuxbrew/.linuxbrew/lib/pkgconfig:/usr/lib/x86_64-linux-gnu/pkgconfig:/usr/share/pkgconfig:&lt;span class="nv"&gt;$PKG_CONFIG_PATH&lt;/span&gt;
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nv"&gt;QT6DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/opt/qt/6.6.3/gcc_64
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;$QT6DIR&lt;/span&gt;/bin &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
  &lt;span class="c"&gt;# for zsh:&lt;/span&gt;
  &lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="nv"&gt;$QT6DIR&lt;/span&gt;/bin &lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="c"&gt;# for bash:&lt;/span&gt;
  &lt;span class="c"&gt;# export PATH="$QT6DIR/bin:$PATH"&lt;/span&gt;
  &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PKG_CONFIG_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$QT6DIR&lt;/span&gt;/lib/pkgconfig:&lt;span class="nv"&gt;$PKG_CONFIG_PATH&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Install kde-builder
&lt;/h2&gt;

&lt;p&gt;Follow the &lt;a href="https://invent.kde.org/sdk/kde-builder#installation"&gt;initial instructions here&lt;/a&gt; at the &lt;code&gt;Generic Installation&lt;/code&gt; section only.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;kde-builder --install-distro-packages&lt;/code&gt; doesn't seem much use as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The qt6 package versions it installed are out of date.&lt;/li&gt;
&lt;li&gt;It doesn't install most of the required dependencies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I did run it and then manually uninstalled the qt6 dependencies since qt6 was already installed and configured using the steps above.&lt;/p&gt;

&lt;p&gt;Then run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kde-builder &lt;span class="nt"&gt;--generate-config&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After this I edited &lt;code&gt;~/.config/kdesrc-buildrc&lt;/code&gt; to install to &lt;code&gt;/opt/plasma6&lt;/code&gt; by changing the line starting &lt;code&gt;install-dir&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;install-dir /opt/plasma6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And created this directory with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; /opt/plasma6
&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nv"&gt;$USER&lt;/span&gt; /opt/plasma6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Add some unavailable python dependencies
&lt;/h2&gt;

&lt;p&gt;Create a directory to store the dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo mkdir /opt/python
sudo chown $USER /opt/python
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add this path to &lt;code&gt;PYTHONPATH&lt;/code&gt; in your shell config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if [ -d /opt/python ]; then
  export PYTHONPATH=/opt/python/local/lib/python$(python3 --version | grep -o '3.[0-9]*')/dist-packages
fi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then install them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install --prefix /opt/python chai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Be careful that &lt;code&gt;brew&lt;/code&gt; has not installed python otherwise you'll have something like &lt;code&gt;python3&lt;/code&gt; pointing to your system python and &lt;code&gt;python3.12&lt;/code&gt; pointing to brew's version, then the build scripts will detect brew's version and not see your system packages. I just uninstalled &lt;code&gt;brew&lt;/code&gt;'s version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start installing
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kde-builder workspace
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will inevitably fail many times and give the location of a log that indicates the error, which is usually a missing package. When installing that package use one with a &lt;code&gt;-dev&lt;/code&gt; suffix.&lt;/p&gt;

&lt;p&gt;After installing the package run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kde-builder &lt;span class="nt"&gt;--resume-from&lt;/span&gt; &amp;lt;name of package that failed&amp;gt; workspace
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To resume building from where it left off.&lt;/p&gt;

&lt;p&gt;I needed the following additional packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;libxkbcommon-dev libpolkit-gobject-1-dev libpolkit-agent-1-dev libxcb-damage0-dev libyaml-dev libsystemd-dev libstemmer-dev itstool libpcap-dev libsensors-dev libxcb-dpms0-dev libpipewire-0.3-dev libxcb-xtest0-dev libpam0g-dev libeis-dev libxcvt-dev liblcms2-dev libopenjp2-7 libappimage-dev libattr1-dev libnm-dev libqrencode-dev libdmtx-dev libzxing-dev libqalculate-dev libxcursor-dev libxtst-dev libpulse-dev modemmanager-dev python3-cairo libgtk-3-dev libxxf86vm-dev wl-clipboard libaccounts-glib-dev libsdl2-dev libxkbregistry-dev xserver-xorg-input-libinput-dev xserver-xorg-input-evdev-dev xorg-dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also had to install a perl cpan module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cpan install URI::Escape
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This worked for me because I had perl installed via &lt;code&gt;brew&lt;/code&gt; (I haven't used perl for over 20 years, I guess it was a dependency of a package I did use), otherwise &lt;code&gt;sudo&lt;/code&gt; may be necessary at the risk of polluting your &lt;code&gt;/usr&lt;/code&gt; directory with files not managed by &lt;code&gt;apt&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kaccounts&lt;/code&gt; seems to be missing from the dependency list of &lt;code&gt;plasma-desktop&lt;/code&gt; so at some point I had to install this manually via:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kde-builder kaccounts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before resuming:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kde-builder &lt;span class="nt"&gt;--resume-from&lt;/span&gt; plasma-desktop workspace
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Create a startup script
&lt;/h1&gt;

&lt;p&gt;Create the following file at &lt;code&gt;/opt/plasma6/bin/start-kde6&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class="c"&gt;# replace $USER with the location of the user's home directory&lt;/span&gt;
&lt;span class="c"&gt;# who built KDE6&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="nv"&gt;$USER&lt;/span&gt;/.config/kde-env-master.sh

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DBUS_SESSION_BUS_ADDRESS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;exec &lt;/span&gt;dbus-run-session startplasma-wayland
&lt;span class="k"&gt;else
  &lt;/span&gt;&lt;span class="nb"&gt;exec &lt;/span&gt;startplasma-wayland
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ensure it is executable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;a+x /opt/plasma6/bin/start-kde6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Add desktop entry to &lt;code&gt;sddm&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Create the following file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo touch&lt;/span&gt; /usr/share/wayland-sessions/plasmawayland6.desktop
&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nv"&gt;$USER&lt;/span&gt; /usr/share/wayland-sessions/plasmawayland6.desktop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Desktop Entry]
Exec=/opt/plasma6/bin/start-kde6
DesktopNames=KDE6
Name=Plasma 6 (Wayland)
Comment=Plasma 6 by KDE
X-KDE-PluginInfo-Version=6.0.6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Start kde6
&lt;/h2&gt;

&lt;p&gt;Logout of your window manager and select the plasma 6 entry from sddm and see if it starts.&lt;/p&gt;

&lt;p&gt;For me it didn't.&lt;/p&gt;

&lt;p&gt;I got a black screen, the mouse cursor could be moved. Using my &lt;code&gt;krunner&lt;/code&gt; keybinding I noticed some glitches at the top of the screen where &lt;code&gt;krunner&lt;/code&gt; would usually appear. I could switch to another virtual console via &lt;code&gt;ctrl+alt+f3&lt;/code&gt; to restart sddm, after this trying to start plasma6 again froze my computer and I had to reboot it. When I switched back to kde5, most of my keybindings were gone. Luckily I backed them up several kde config files before switching over and could restore them.&lt;/p&gt;

&lt;p&gt;When I was in the virtual console and plasma6 was running, I noticed it had started &lt;code&gt;/usr/bin/plasmashell&lt;/code&gt; instead of &lt;code&gt;/opt/plasma6/bin/plasmashell&lt;/code&gt;. Disappointing. I verified that &lt;code&gt;PATH&lt;/code&gt; and &lt;code&gt;XDG_CONFIG_DIRS&lt;/code&gt; were set properly by looking at the environment of the &lt;code&gt;startplasma-wayland&lt;/code&gt; process in the &lt;code&gt;/proc&lt;/code&gt; filesystem, so why it's still choosing &lt;code&gt;/usr/bin/plasmashell&lt;/code&gt; I don't know.&lt;/p&gt;

&lt;p&gt;Some of my code is in KDE, for the notification system and scripting system. I'd love to be able to contribute again. The matrix chat for KDE is not open for registration so I'm not sure how I can get help to solve this problem.&lt;/p&gt;

&lt;p&gt;I'm using the &lt;code&gt;nvidia-drivers&lt;/code&gt; currently at version 550. Wayland on &lt;code&gt;plasma5&lt;/code&gt; is okay, but copy and paste is not reliable. Especially in slack. Sometimes it works for a while and then breaks forever. To paste into my terminal I usually need to hit paste several times for the very first paste into that terminal, after which point it works.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>A sequential work queue for typescript in 11 lines of code</title>
      <dc:creator>insidewhy</dc:creator>
      <pubDate>Mon, 20 Feb 2023 08:06:07 +0000</pubDate>
      <link>https://forem.com/insidewhy/a-sequential-work-queue-for-typescript-in-10-lines-of-code-29ol</link>
      <guid>https://forem.com/insidewhy/a-sequential-work-queue-for-typescript-in-10-lines-of-code-29ol</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WorkQueue&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&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;private&lt;/span&gt; &lt;span class="na"&gt;lastJob&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;queueWork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;work&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&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;nextJob&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;lastJob&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;lastJob&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;work&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&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="o"&gt;??&lt;/span&gt; &lt;span class="nf"&gt;work&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;nextJob&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// ensure previous results can be garbage collected&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nextJob&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;lastJob&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;lastJob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;undefined&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;Okay maybe you should use a few more lines to be clearer but I like how neat this is.&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>productivity</category>
      <category>strategygames</category>
    </item>
    <item>
      <title>A nice work queue for TypeScript/JavaScript</title>
      <dc:creator>insidewhy</dc:creator>
      <pubDate>Wed, 11 Jan 2023 11:35:58 +0000</pubDate>
      <link>https://forem.com/insidewhy/a-nice-work-queue-for-typescriptjavascript-2439</link>
      <guid>https://forem.com/insidewhy/a-nice-work-queue-for-typescriptjavascript-2439</guid>
      <description>&lt;p&gt;This posts is about an open source work queue library I wrote for processing data at my current job: &lt;a href="https://github.com/insidewhy/horse-sparkle"&gt;horse-sparkle&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Allows adding work to a queue to be processed one at a time and in order.&lt;/li&gt;
&lt;li&gt;Puts work to the back of the queue when it fails.&lt;/li&gt;
&lt;li&gt;Resumes work from where it failed rather than having to restart it from scratch (via the power of async iterators).&lt;/li&gt;
&lt;li&gt;Allows limiting the size of the queue to avoid unbounded memory usage:

&lt;ul&gt;
&lt;li&gt;Context is associated with each piece of work, after failure the memory associated with the work is reclaimed but the context can be used to recreate it from scratch.&lt;/li&gt;
&lt;li&gt;Overflows are consumed from an async generator, the user may recreate the work from the associated context when the queue is empty, throw it away, or perform any other action they want.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Can customise the behaviour which occurs when a failure is detected:

&lt;ul&gt;
&lt;li&gt;The queue awaits the error handler which can be used to add delays on failure.&lt;/li&gt;
&lt;li&gt;The consecutive failure count is passed to the error handler which may be used for exponential backoff.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;horse-sparkle&lt;/code&gt; is written in TypeScript and the project includes extensive unit testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Documentation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Basic usage
&lt;/h3&gt;

&lt;p&gt;It can be useful to create a queue of work, and on error, it may not be great to restart the entire piece of work from the beginning. &lt;code&gt;horse-sparkle&lt;/code&gt; enables this pattern:&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;yieldErrors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;WorkQueue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;WorkIterator&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="s1"&gt;horse-sparkle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;doWork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dbId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;WorkIterator&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;largeObject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;yieldErrors&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;grabLargeObjectFromDatabase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dbId&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;bigResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;yieldErrors&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;doExpensiveProcessingOnObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;largeObject&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;yieldErrors&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;doFinalProcessingOfBigResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bigResult&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Here the &amp;lt;string&amp;gt; generic should be the context parameter, we will see how&lt;/span&gt;
&lt;span class="c1"&gt;// this is used later&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;WorkQueue&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&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;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;doWorkInTurn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dbId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// The dbId relates to the &amp;lt;string&amp;gt; generic used above&lt;/span&gt;
  &lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queueWork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dbId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;doWork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dbId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function &lt;code&gt;yieldErrors&lt;/code&gt; will continuously run the function passed to it until it does not throw an error. Any thrown error will be yielded by the generator, this causes the work queue to suspend the task. The work queue will then push the piece of work to the back of the queue and once the work is back at the head of the queue it will resume the task from where it left off e.g. If &lt;code&gt;largeObject&lt;/code&gt; was retrieved and &lt;code&gt;bigResult&lt;/code&gt; was calculated, but then &lt;code&gt;doFinalProcessingOfBigResult&lt;/code&gt; threw an error, when the work is tried the next time it will not have to calculate &lt;code&gt;bigResult&lt;/code&gt; again, instead the work will resume with another attempt to call &lt;code&gt;doFinalProcessingOfBigResult&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Error behaviour
&lt;/h3&gt;

&lt;p&gt;In the previous example the work queue will continuously process information without delay. In many instances this would not be good, for example a database may be down and it would not be desirable to continuously attempt to access it. This can be solved using the &lt;code&gt;onError&lt;/code&gt; parameter to &lt;code&gt;WorkQueue&lt;/code&gt;:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;WorkQueue&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;onError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;consecutiveErrorCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Work queue error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;consecutiveErrorCount&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;400&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;The return value of the &lt;code&gt;onError&lt;/code&gt; handler is awaited before processing the next queue item, with the function used above the first delay would be &lt;code&gt;100ms&lt;/code&gt;, the second would be &lt;code&gt;200ms&lt;/code&gt; and subsequent delays would be &lt;code&gt;400ms&lt;/code&gt;. The &lt;code&gt;consecutiveErrorCount&lt;/code&gt; is reset to &lt;code&gt;1&lt;/code&gt; after a piece of work successfully completes, so an error received after a successful completion would cause a &lt;code&gt;100ms&lt;/code&gt; delay.&lt;/p&gt;

&lt;h3&gt;
  
  
  Limiting the queue length
&lt;/h3&gt;

&lt;p&gt;In the first example the object &lt;code&gt;bigResult&lt;/code&gt; may take up significant amount of memory. If the size of the work queue was allowed to grow indefinitely and many failures were detected this could result in memory running out. The &lt;code&gt;WorkQueue&lt;/code&gt; accepts a &lt;code&gt;maxQueueLength&lt;/code&gt; parameter for dealing with this.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;WorkQueue&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;maxQueueLength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However it would probably not be useful to completely throw the work away, instead it may be good to reclaim the memory associated with the async iterator and create the work again from scratch later. The context parameter can then be used to do this, it will be more expensive to repeat the processing but at least the memory usage will have a maximum limit. To facilitate this &lt;code&gt;WorkQueue&lt;/code&gt; provides a method &lt;code&gt;overflows&lt;/code&gt; that returns an async iterator which yields the context related with each piece of work that was thrown out of the queue due to an overflow. The &lt;code&gt;waitForSpaceInWorkQueue&lt;/code&gt; method can be used to asynchronously wait for the queue to have space in order to requeue the work if desired.&lt;br&gt;
Repeating the example above:&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;yieldErrors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;WorkQueue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;WorkIterator&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="s1"&gt;horse-sparkle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;doWork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dbId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;WorkIterator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;largeObject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Massive&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;yieldErrors&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;grabLargeObjectFromDatabase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dbId&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;bigResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;yieldErrors&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;doExpensiveProcessingOnObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;largeObject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;// reclaim memory from largeObject now it is no longer needed&lt;/span&gt;
  &lt;span class="nx"&gt;largeObject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;yieldErrors&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;doFinalProcessingOfBigResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bigResult&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;WorkQueue&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&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;function&lt;/span&gt; &lt;span class="nx"&gt;doWorkInTurn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dbId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queueWork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dbId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;doWork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dbId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;overflowHandler&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;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;for&lt;/span&gt; &lt;span class="k"&gt;await&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;dbId&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;overflows&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// this is one suggestion enabled by the API in which the work is eventually&lt;/span&gt;
    &lt;span class="c1"&gt;// pushed to the back of the queue to be resumed from scratch&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waitForSpaceInWorkQueue&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queueWork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dbId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;doWork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dbId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;startQueue&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nx"&gt;overflowHandler&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;h3&gt;
  
  
  Stopping the queue
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;stop&lt;/code&gt; method may be called on the queue to stop processing. This return a promise which resolves after the current piece of asynchronous work performed by the queue succeeds or fails. The promise returned by the &lt;code&gt;start&lt;/code&gt; method also resolves at this point, and any async iterator created by the &lt;code&gt;overflows&lt;/code&gt; method will be completed.&lt;/p&gt;

</description>
      <category>queue</category>
      <category>javascript</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Advent of code 2022 - each solution in a different language</title>
      <dc:creator>insidewhy</dc:creator>
      <pubDate>Thu, 29 Dec 2022 07:16:41 +0000</pubDate>
      <link>https://forem.com/insidewhy/advent-of-code-2022-each-solution-in-a-different-language-51p</link>
      <guid>https://forem.com/insidewhy/advent-of-code-2022-each-solution-in-a-different-language-51p</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/insidewhy/advent-of-code-2022" rel="noopener noreferrer"&gt;I added my solutions to a github repo&lt;/a&gt;. I've done days 1-11 so far. I'll blog about the rest in a follow up post once I get around to them.&lt;/p&gt;

&lt;h2&gt;
  
  
  ruby
&lt;/h2&gt;

&lt;p&gt;The first problem was very easy, in each case two lines of code. There's a lot I like about Ruby but I don't like the implementation of the typesystem that was added in 2020. In order to add types all classes/functions etc. must be repeated in a second file where types are allowed.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I don't like having to switch back and forth between my code and the type definition file to lookup types, types are such a useful part of the documentation and the code itself that I feel like they should be colocated.&lt;/li&gt;
&lt;li&gt;"Repeat yourself twice" instead of DRY is kind of sad for a language that is usually expressive.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  crystal
&lt;/h2&gt;

&lt;p&gt;Crystal is a compiled version of Ruby where the types are colocated, it's not 100% compatible but close enough.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I'd love to be able to use this language more but it doesn't seem to be getting much attention.&lt;/li&gt;
&lt;li&gt;I wish I could use const variables and mark data as immutable.&lt;/li&gt;
&lt;li&gt;The compiler is a little slow.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  TypeScript
&lt;/h2&gt;

&lt;p&gt;I use TypeScript more than any other language in the last few years so it was easy to write this without having to reference any documentation. The problem wasn't very hard but there were two small areas where you could use modular arithmetic in a fun way. I checked some other solutions online and many people just used a lookup table where looked up each line from the file against a score table and this works for both parts since there are only six combinations of hands.&lt;/p&gt;

&lt;h2&gt;
  
  
  c++
&lt;/h2&gt;

&lt;p&gt;C++ gets a lot of hate for being too complicated and for baggage it inherited from C. On the other hand a lot of people dislike the more recent versions of C++ and only want to use classic C++, the version where it's hard to avoid that baggage. I wonder if the most pragmatic solution to keep things simple is to take the opposite approach to both these camps and disregard earlier features rather than modern features. The solution here is quite neat and performs very well.&lt;/p&gt;

&lt;h2&gt;
  
  
  dart
&lt;/h2&gt;

&lt;p&gt;Dart is a very conservative language so solutions tend to be quite verbose. This conservatism extends to the typesystem so it can be harder to write typesafe programs without resorting to repetition that can be avoided in other languages. TypeScript is a lot more expressive in this respect. I find it a little joyless to use dart, both for this solution and in my day job where I occasionally write flutter apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  go
&lt;/h2&gt;

&lt;p&gt;Not much to say about go that's different from dart, I don't like writing go so much as it feels very soulless and a little patronising. It feels like a language that was written in which to achieve day jobs, the fun you get is from solving business problems and not from the joy of writing code itself. I'm not really sure if the verbosity of the code needed to achieve simple things really is a boon for maintainable software. Surely there's a balance between having to write a lot of boilerplate and writing understandable code? Error handling in go is a &lt;strong&gt;PAIN&lt;/strong&gt;. Rust doesn't have exceptions either but they added some nice syntax to the language for dealing with errors.&lt;/p&gt;

&lt;h2&gt;
  
  
  haskell
&lt;/h2&gt;

&lt;p&gt;Haskell is possibly the opposite of dart/go, the expressiveness of the language is off the charts. I'm not sure if it's a pragmatic choice for most of the world, Haskell code can be impenetrable even to the people that wrote it. There's a fine balance between simplicity, verbosity and how fun a language is to use and that depends a lot on your own experience and personality. For me it's somewhere closer to haskell than go but still very far away from either.&lt;/p&gt;

&lt;h2&gt;
  
  
  scala
&lt;/h2&gt;

&lt;p&gt;I had so much fun with this one. Maybe scala strikes the expressiveness/readability balance for me. There were a lot of instances where I was really impressed at how readable and tersely I could express things.&lt;/p&gt;

&lt;h2&gt;
  
  
  php
&lt;/h2&gt;

&lt;p&gt;I was kind of interested to see how far php had come and ended up disappointed. The typechecking happens at runtime rather than via static analysis!?!?!? As usual, the designers of php seem to want things from a programming language that are completely foreign to me. The way constants are defined is still annoying. The amount of &lt;code&gt;$&lt;/code&gt; symbols in php code is still annoying. They took a lot of nice things from JavaScript and TypeScript that make it more expressive than it used to be but I still strongly dislike it.&lt;/p&gt;

&lt;h2&gt;
  
  
  rust
&lt;/h2&gt;

&lt;p&gt;Rust is pretty fun, I've used it a little in the past also. The borrow checker is cool but it might really annoy you until you understand it well. The macro system is very fun and the shortcuts for error handling are very nice. It forced me to use integers to index vectors instead of iterators which made some of the code less readable than it could be, but maybe it's worth it to have code that is guaranteed to be safe.&lt;/p&gt;

&lt;h2&gt;
  
  
  raku
&lt;/h2&gt;

&lt;p&gt;raku is the successor to perl 5 which I learned when I was 16 (after Metacomco basic on the Atari ST and bash). I haven't used perl in about 15 years and I think it was helpful having some distance because, while raku is reminiscent of perl 5, it's unmistakeably a very different language. I thought it would be fun to use it for solution 11 as it requires a lot of parsing and raku has the ability to define grammars built into the language (it operates much like a very advanced version of regexes). There were things I loved about raku and things that confounded me. The typesystem is a little limiting (no variants, no parametric types). The difference between constructors and the &lt;code&gt;BUILD&lt;/code&gt; method was also a little frustrating, you can only assign to private attributes in the latter but this involves duplicating the names of attributes you want to assign directly from arguments. The amount of sigils in the code also makes it a little difficult to type; they call them twigils in the documentation. A lot of stuff in raku works (or is named, or is expressed) very differently than you might be used to. It was fun to learn and use but definitely the language where I hit the most unexpected results and that required the most learning and research. It's doubtful I'll get to use it again in the future due to its niche popularity. One thing I really liked about part 2 of this solution is that it relies on your knowledge of modular arithmetic in order to produce a solution that runs in a reasonable length of time. Without applying this then you'll really start hitting the performance limits of big integers and the code will take several days to provide a solution.&lt;/p&gt;

</description>
      <category>php</category>
      <category>programming</category>
    </item>
    <item>
      <title>An excellent way to deal with (lerna) monorepos in CircleCI: circletron</title>
      <dc:creator>insidewhy</dc:creator>
      <pubDate>Fri, 25 Jun 2021 11:25:23 +0000</pubDate>
      <link>https://forem.com/insidewhy/an-excellent-way-to-deal-with-monorepos-in-circleci-46kf</link>
      <guid>https://forem.com/insidewhy/an-excellent-way-to-deal-with-monorepos-in-circleci-46kf</guid>
      <description>&lt;p&gt;We started with a monorepo at my current company and have been using circle almost since the beginning. It was tough. It required a lot of boilerplate in our config and the necessity to get every developer to generate a circle API key and add it to git. It never felt that great but at least it worked. In April CircleCI released the &lt;a href="https://circleci.com/docs/2.0/dynamic-config/"&gt;dynamic configuration API&lt;/a&gt; and this allowed us to refactor our monorepo support into something we think is pretty great. We've gone from tolerating Circle to enjoying it, and now we've released the code as an open source project and provided the functionality as an &lt;a href="https://circleci.com/orbs/"&gt;orb&lt;/a&gt; so that any project can benefit.&lt;/p&gt;

&lt;p&gt;The project is circletron, the code is hosted &lt;a href="https://github.com/circletron/circletron"&gt;on github&lt;/a&gt; and the &lt;a href="https://circleci.com/developer/orbs/orb/circletron/circletron"&gt;orb is here&lt;/a&gt;. It currently supports lerna monorepos but we plan to support other monorepos in the future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Motivating example
&lt;/h2&gt;

&lt;p&gt;Typically a circleci configuration exists in a single file within a repository at &lt;code&gt;.circleci/config.yml&lt;/code&gt;. For a monorepo a very minimal example may look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2.1&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;validate-everything&lt;/span&gt;&lt;span class="pi"&gt;:&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="s"&gt;checkout&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 general-validation&lt;/span&gt;

  &lt;span class="na"&gt;test-subpackage-a&lt;/span&gt;&lt;span class="pi"&gt;:&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="s"&gt;checkout&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;cd packages/package-a &amp;amp;&amp;amp; npm run test&lt;/span&gt;

  &lt;span class="na"&gt;test-subpackage-b&lt;/span&gt;&lt;span class="pi"&gt;:&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="s"&gt;checkout&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;cd packages/package-b &amp;amp;&amp;amp; npm run test&lt;/span&gt;

  &lt;span class="na"&gt;test-subpackage-c&lt;/span&gt;&lt;span class="pi"&gt;:&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="s"&gt;checkout&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;cd packages/package-c &amp;amp;&amp;amp; npm run test&lt;/span&gt;

  &lt;span class="na"&gt;publish-subpackage-c&lt;/span&gt;&lt;span class="pi"&gt;:&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="s"&gt;checkout&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;cd packages/package-c &amp;amp;&amp;amp; npm run publish&lt;/span&gt;


&lt;span class="na"&gt;workflows&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;validate-everything&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="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;validate-everything&lt;/span&gt;

  &lt;span class="na"&gt;subpackage-a&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="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;test-subpackage-a&lt;/span&gt;

  &lt;span class="na"&gt;subpackage-b&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="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;test-subpackage-b&lt;/span&gt;

  &lt;span class="na"&gt;subpackage-c&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="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;test-subpackage-c&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;publish-subpackage-c&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Obviously there are many problems with this, for a start all the CI is defined in one file at the root of the project. Worse, each of the jobs may take some time to complete and on every commit to the project, circleci will run every single job no matter whether there are any changes to the respective subpackages or not. It may not look so bad in this tiny example but the bigger the configuration gets, the worse it is to deal with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using circletron to solve this
&lt;/h2&gt;

&lt;p&gt;With &lt;code&gt;circletron&lt;/code&gt; the &lt;code&gt;.circle/config.yml&lt;/code&gt; is always the same:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2.1&lt;/span&gt;
&lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;orbs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;circletron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;circletron/circletron@3.0.1&lt;/span&gt;

&lt;span class="na"&gt;workflows&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;trigger-jobs&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="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;circletron/trigger-jobs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The trigger job step will take many individual &lt;code&gt;circle.yml&lt;/code&gt; distributed within the project and combine them into a single configuration which will be issued via the continuation API. It will modify the configuration to ensure that jobs that are not necessary are no longer run, in a way that is friendly to CI branch protection rules.&lt;/p&gt;

&lt;p&gt;The single configuration file can now be split up across the monorepo, with one optional &lt;code&gt;circle.yml&lt;/code&gt; in the root of the project and where the CI for each subpackage lives in the directory for that subpackage.&lt;/p&gt;

&lt;p&gt;In this instance the &lt;code&gt;circle.yml&lt;/code&gt; in the root of the project will host configuration not specific to any subpackage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2.1&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;validate-everything&lt;/span&gt;&lt;span class="pi"&gt;:&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="s"&gt;checkout&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 general-validation&lt;/span&gt;

&lt;span class="na"&gt;workflows&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;validate-everything&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="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;validate-everything&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The jobs here are run on every commit and it's also a good place to set &lt;code&gt;version&lt;/code&gt;. It's also a good place to provide &lt;code&gt;commands&lt;/code&gt; that can be used in subpackage specific &lt;code&gt;circle.yml&lt;/code&gt; files.&lt;/p&gt;

&lt;p&gt;Now lets look at &lt;code&gt;packages/package-a/circle.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test-subpackage-a&lt;/span&gt;&lt;span class="pi"&gt;:&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="s"&gt;checkout&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;cd packages/package-a &amp;amp;&amp;amp; npm run test&lt;/span&gt;

&lt;span class="na"&gt;workflows&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;subpackage-a&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="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;test-subpackage-a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything related to this package all in one place. Even better, when the PR contains no changes within &lt;code&gt;packages/package-a&lt;/code&gt; the jobs for this subpackage will be skipped. You will probably want to use branch protection rules to ensure that when the &lt;code&gt;test-subpackage-a&lt;/code&gt; job fails the PR will be blocked so omitting this job entirely would not be ideal. Omitted jobs remain in &lt;code&gt;pending&lt;/code&gt; state permanently, blocking the PR. circletron replaces unneeded jobs with a simple job using the &lt;code&gt;busybox:stable&lt;/code&gt; docker image that echoes &lt;code&gt;"Job not required"&lt;/code&gt; and issues a success error code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dependencies and unconditional jobs
&lt;/h2&gt;

&lt;p&gt;What if the code in &lt;code&gt;packages/subpackage-c&lt;/code&gt; uses code from &lt;code&gt;packages/subpackage-a&lt;/code&gt;? In this case the jobs within this package should run when the code in &lt;code&gt;subpackage-a&lt;/code&gt; changes, even if the code in the subpackage itself doesn't change.&lt;/p&gt;

&lt;p&gt;There may also be some instances where a job should run on every push.&lt;/p&gt;

&lt;p&gt;The configuration at &lt;code&gt;packages/subpackage-c/circle.yml&lt;/code&gt; shows how to add dependencies and create jobs which run unconditionally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;subpackage-a&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;test-subpackage-c&lt;/span&gt;&lt;span class="pi"&gt;:&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="s"&gt;checkout&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;cd packages/package-b &amp;amp;&amp;amp; npm run test&lt;/span&gt;

  &lt;span class="na"&gt;publish-subpackage-c&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;conditional&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&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="s"&gt;checkout&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;cd packages/package-c &amp;amp;&amp;amp; npm run publish&lt;/span&gt;

&lt;span class="na"&gt;workflows&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;subpackage-c&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="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;test-subpackage-c&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;publish-subpackage-a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this instance &lt;code&gt;test-subpackage-c&lt;/code&gt; will only be run when changes to &lt;code&gt;package-a&lt;/code&gt; or &lt;code&gt;package-c&lt;/code&gt; are detected and &lt;code&gt;publish-subpackage-c&lt;/code&gt; will run on every push.&lt;/p&gt;

&lt;h2&gt;
  
  
  Target branches
&lt;/h2&gt;

&lt;p&gt;CircleCI does not pass the target branch to workflows or jobs so it becomes necessary to help circletron out. By default circletron considers the branches &lt;code&gt;main&lt;/code&gt;, &lt;code&gt;master&lt;/code&gt;, &lt;code&gt;develop&lt;/code&gt; and any branch starting with &lt;code&gt;release/&lt;/code&gt; as a target branch. The latest commit from the branch commit history which belongs to one of these branches is considered to be the branch-point. For pushes to one of the branches above, all of the jobs are run. This can be changed via the configuration file at &lt;code&gt;.circleci/circletron.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;targetBranches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^(release/|main$|master$|develop$)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any branch which matches this regex is considered a target branch, the above configuration shows the default.&lt;/p&gt;

&lt;p&gt;circletron is available to use now.&lt;/p&gt;

</description>
      <category>circleci</category>
      <category>monorepo</category>
      <category>circletron</category>
      <category>lerna</category>
    </item>
    <item>
      <title>How to deal with the first few months of a new job writing software</title>
      <dc:creator>insidewhy</dc:creator>
      <pubDate>Tue, 01 Jun 2021 18:06:28 +0000</pubDate>
      <link>https://forem.com/insidewhy/tips-for-starting-a-new-programming-job-n2c</link>
      <guid>https://forem.com/insidewhy/tips-for-starting-a-new-programming-job-n2c</guid>
      <description>&lt;p&gt;Read &lt;code&gt;git log&lt;/code&gt; and see how people write their commits. Are they all in imperative mood? Then write your commits in imperative mood. Do they start with a capital letter? Do the same. Include a cat emoji after every word? Now you're stuck with that too. But what if every commit message follows a completely different style? Then you're lucky, you have a chance to advocate for a standard, to be recognised as someone who's trying to bring some order to chaos.&lt;/p&gt;

&lt;p&gt;The next thing you could avoid if you were a master of Zen, but you are not, so you're going to panic for at least two months. There is going to be horrible crazy code and horrible design and you're going to question what crazy incompetent programmers could commit such crimes against code. But recognise that there are probably good reasons for most of these horrible things you see, and realise that panic for at least the first few months in a new job is a completely normal and understandable reaction. Instead of criticising, ask questions. When you get the answers you'll often find that the crazy design idea was actually a reasonable and pragmatic work-around. Or that the other developers around you have recognised the same code debt and also have a plan to fix it. If you get defensive and criticise the code you stand the chance of making enemies but if you ask good questions there's a good chance people will recognise and appreciate your ideas and feedback.&lt;/p&gt;

&lt;p&gt;You're going to get code reviews, the second time you get a change request for the same type of issue you've already received a change request for in the past, it's probably going to stick in your head. Write it down somewhere so you don't do the same thing a third time. Being caught repetitively making the same "mistake" might be seen as a lack of attention to detail.&lt;/p&gt;

&lt;p&gt;Make sure you respond to all change requests in your PR reviews. If you're using GitHub, then see the next tip, if not then maybe you'll have to keep track of this yourself and make sure you go through all your pull requests each morning manually. Respond to every single change request you get, even if it's just to add an "eyes" emoji or a thumbs up. If you disagree with a change request, respond with a comment and keep following up until you reach consensus. Everyone has worked a developer who says "I have nothing to do" despite having multiple change requests open against their pull requests. It's very frustrating. Every change request is "something to do", sometimes you might have to be proactive in following up your change requests to reach a resolution but that's one of the most important parts of a development job.&lt;/p&gt;

&lt;p&gt;If your company is using GitHub then before you do anything else, learn how GitHub notifications work. With an adequate understanding of this you'll never miss another piece of information relating to your code again, then all you need to worry about is how to keep up with your project management tool.&lt;/p&gt;

&lt;p&gt;Update your issue statuses in your project management tool at least once every morning. Leaving code that's been merged in &lt;code&gt;In Progress&lt;/code&gt; state can be tolerate for up to a day, but any more than this looks lazy. 90% of your communication might be with other developers but advocating for, and facilitating transparency amongst the organisation will be recognised beyond your team.&lt;/p&gt;

</description>
      <category>jobs</category>
      <category>software</category>
      <category>advice</category>
    </item>
    <item>
      <title>Filtering the types of a tuple at compile time in typescript</title>
      <dc:creator>insidewhy</dc:creator>
      <pubDate>Sat, 18 Apr 2020 17:26:56 +0000</pubDate>
      <link>https://forem.com/insidewhy/filtering-the-types-of-a-tuple-at-compile-time-in-typescript-1bf2</link>
      <guid>https://forem.com/insidewhy/filtering-the-types-of-a-tuple-at-compile-time-in-typescript-1bf2</guid>
      <description>&lt;p&gt;I needed to remove all of the boolean types from a tuple for a parser generator I'm writing. The code turned out to be way crazier than I would have liked:&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="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;NumberMap&lt;/span&gt; &lt;span class="o"&gt;=&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="mi"&gt;1&lt;/span&gt;
  &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
  &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
  &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
  &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;
  &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;
  &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;
  &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;
  &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
  &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;
  &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;
  &lt;span class="c1"&gt;// up to twelve supported&lt;/span&gt;
  &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// prepending is relatively easy&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Prepend&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;H&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&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="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;H&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;infer&lt;/span&gt; &lt;span class="nx"&gt;L&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;L&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;

&lt;span class="c1"&gt;// appending is possible but expensive and hard, must&lt;/span&gt;
&lt;span class="c1"&gt;// build lists in reverse and reverse the result when done&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Reverse&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;L&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&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="o"&gt;=&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;R&lt;/span&gt;
  &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;((...&lt;/span&gt;&lt;span class="na"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;L&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;infer&lt;/span&gt; &lt;span class="nx"&gt;H&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="na"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;infer&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;Reverse&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Prepend&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;H&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;
&lt;span class="p"&gt;}[&lt;/span&gt;&lt;span class="nx"&gt;L&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[]]&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;1&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="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Equals&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;I&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;L&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;I&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;L&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;FilterBoolean&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&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="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Prepend&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;FilterBooleansNext&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
  &lt;span class="nx"&gt;I&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;NumberMap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="nx"&gt;R&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&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="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt;
  &lt;span class="p"&gt;:&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;FilterBooleansNext&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;NumberMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;I&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;FilterBoolean&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;I&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
      &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt;
    &lt;span class="p"&gt;}[&lt;/span&gt;&lt;span class="nx"&gt;Equals&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;I&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;length&lt;/span&gt;&lt;span class="dl"&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="c1"&gt;// append is hard/expensive, so keep prepending and reverse the result&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;FilterBooleans&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&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="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Reverse&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
  &lt;span class="nx"&gt;FilterBooleansNext&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&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;T&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="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Oh boy. Going through it bit by bit:&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="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;NumberMap&lt;/span&gt; &lt;span class="o"&gt;=&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="mi"&gt;1&lt;/span&gt;
  &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
  &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
  &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
  &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;
  &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;
  &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;
  &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;
  &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
  &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;
  &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;
  &lt;span class="c1"&gt;// up to twelve supported&lt;/span&gt;
  &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Can't do &lt;code&gt;I + 1&lt;/code&gt; in a typescript type. Nope. So instead there is this map. The last map item has to point to itself or the code won't compile, because even when no tuples with 12 items are used, the code will still instantiate up the limit and then complain about item 13 not existing.&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="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Prepend&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;H&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&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="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;H&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;infer&lt;/span&gt; &lt;span class="nx"&gt;L&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;L&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prepends a type to the head of a list. It's a bit disappointing that it's necessary to use a function just to infer the prepended type but that's how typescript is. This is used to build the "result type". You're probably thinking, isn't it better to append types gradually when building the result? Well yeah, but writing an append type turns out to be far more complicated and much more expensive, it involves reversing the list twice. No way, this metaprogram already uses up a ridiculous amount of CPU.&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="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Equals&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;I&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;L&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;I&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;L&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;FilterBoolean&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&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="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Prepend&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;FilterBooleansNext&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
  &lt;span class="nx"&gt;I&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;NumberMap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="nx"&gt;R&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&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="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt;
  &lt;span class="p"&gt;:&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;FilterBooleansNext&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;NumberMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;I&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;FilterBoolean&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;I&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
      &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt;
    &lt;span class="p"&gt;}[&lt;/span&gt;&lt;span class="nx"&gt;Equals&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;I&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;length&lt;/span&gt;&lt;span class="dl"&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;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;FilterBooleans&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&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="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Reverse&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
  &lt;span class="nx"&gt;FilterBooleansNext&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&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;T&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="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Oh dear, why the object and why the &lt;code&gt;Equals&lt;/code&gt;? Why not just &lt;code&gt;I extends T['length'] ? ... : ...&lt;/code&gt;. Well that introduces a recursive reference from &lt;code&gt;FilterBooleansNext&lt;/code&gt; back to itself that typescript just won't tolerate. But it will tolerate this hack. Now the final bit:&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;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;FilterBooleans&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&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="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Reverse&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
  &lt;span class="nx"&gt;FilterBooleansNext&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&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;T&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="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember I said appending was too hard, so we keep pretending types to the result and reverse it at the end.&lt;/p&gt;

&lt;p&gt;The result of all this? While I'm editing my project with this type one of my CPU cores is constantly at 100%. The compile time of my program doubled from 3 seconds to 6. I threw it away and my API suffers for it because now the user has to manually specify types when they shouldn't have to.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>tuples</category>
      <category>metaprogramming</category>
    </item>
  </channel>
</rss>
