Matthew Roach All articles from matthewroach.me 2024-10-08T00:00:00Z https://matthewroach.me/ Matthew Roach feed@matthewroach.me Static Site(s) with Kamal 2024-10-08T00:00:00Z https://matthewroach.me/static-sites-with-kamal/ <p><a href="https://kamal-deploy.org/">Kamal</a> is an open source tool created by <a href="https://37signals.com/">37signals</a> as part thier <a href="https://world.hey.com/dhh/we-have-left-the-cloud-251760fb">cloud exit</a>. That as per their tag line allows you to <strong>Deploy web apps anywhere.</strong> Quite the statement! But, they also say;</p> <blockquote> <p>Originally built for Rails apps, Kamal will work with any type of web app that can be containerized.</p> </blockquote> <p>As you may of noticed they use the term <em>web app</em>. This is great if you have a web app to deploy, but what if you only have a static website to deploy. Is Kamal of no use? You maybe right in thinking its not the tool for you, but you'd be wrong. Kamal is a wrapper around docker, and if you can containerize your static website you can use Kamal to deploy it.</p> <p>Is Kamal overkill for a static site - possibly. But remember Kamal allows you to deploy anywhere, and which Kamal 2 you can host multiple sites on a single server. You can remove the need to use services like Netlify, Vercel and the host of other services out there. Now don't get me wrong, these services are very easy to use and great for hosting static sites, they have some great developer experience (DX) around them to connect various services together to make deployments happen at the click of a button or with seamless automation.</p> <p>Moving from one of these services to uisng Kamal does require more work upfront and a different mindset. You are trading a third party hosting your website to hosting your website on your own server.</p> <p>I used the following steps to deploy a very basic static site, you can see here - <a href="https://staticaml.tealeaves.dev/">https://staticaml.tealeaves.dev</a>.</p> <h2>Prerequisites</h2> <ul> <li>A domain name and ability to change DNS records <ul> <li>Recommend using Cloudflare with SSL termination</li> </ul> </li> <li>A server, using <a href="https://www.hetzner.com/cloud/">Hetzner Cloud</a> server at €4.55. Be sure to set up SSH access via public/private key <ul> <li>You'll need the IP address to the server for Kamal config.</li> </ul> </li> <li>A container registry account, <a href="https://hub.docker.com/">Docker Hub</a> offers 1 free private repository. <ul> <li>You'll need to set up a personal access token for docker hub access</li> </ul> </li> <li>A static website to deploy - source should be within a git repository.</li> <li>Docker installed on your machine</li> <li>Ruby installed on your local machine (not a requirement to use Kamal, but is how I'll explain using Kamal)</li> </ul> <p>For the remainder of this post I am going to assume you already have a static website to deploy, this allows us to focus on setting up Kamal and containerizing the static site.</p> <h2>Containerizing the static site</h2> <p>As per the Kamal website, it will work with any type of web app that can be containerized. Before we get into setting up Kamal we first need containerize our static site. For a static site to be deployed we need to use a web server. There are a number of web servers available, we are going to use <a href="https://nginx.org/">nginx</a>.</p> <p>For the next part I am assuming within your repository you have your static site files within a <code>src</code> directory. Having all the site files within a sub directory will make things a little easier.</p> <p>In the root of your static site repository create a <code>Dockerfile</code> with the following</p> <pre><code class="language-dockerfile">FROM nginx COPY ./src /usr/share/nginx/html EXPOSE 80 </code></pre> <p>A very basic and minimal docker container.</p> <ol> <li><code>FROM nginx</code> - Tells docker to use the <code>nginx</code> image</li> <li><code>COPY ./src /usr/share/nginx/html</code> - Copies our static site files into the webserver directory nginx serves files from</li> <li><code>EXPOSE 80</code> - Describe which ports your application is listening on</li> </ol> <h2>Setting up Kamal</h2> <p>In the root of your repository</p> <ul> <li>Create a <code>.ruby-version</code> file with ruby version - <code>echo &quot;ruby-3.2.2&quot; &gt;&gt; .ruby-version</code></li> <li>Create a <code>Gemfile</code> file - <code>bundle init</code></li> <li>Install kamal - <code>bundle add kamal</code></li> <li>Initialize kamal - <code>kamal init</code></li> </ul> <p>You will now have two new folders added to your repository <code>.kamal</code> and <code>config</code>. Within the config folder there will be a file <code>deploy.yml</code>. This file is where all the configuration settings are set, you will be updating this file with your own settings.</p> <p>Update <code>config/deploy.yml</code> with correct values based on your own accounts.</p> <ul> <li><code>service</code> - a unique name of your application</li> <li><code>image</code> - The container image as per docker hub - <code>{docker-username}/{service}</code></li> <li><code>servers.web</code> - The IP address to the server you want to deploy to</li> <li><code>proxy.ssl</code> - Set this to <code>false</code> as we are using Cloudflare to handle SSL</li> <li><code>proxy.host</code> - The URL of the site - e.g. <code>my-static-site.com</code></li> <li><code>registry.username</code> - Your docker hub username</li> <li><code>env</code> - Update <code>env</code> config to make the environment variable <code>KAMAL_REGISTRY_PASSWORD</code> available to container</li> </ul> <pre><code class="language-yaml"># Name of your application. Used to uniquely configure containers. service: staticaml # Name of the container image. image: matthewroach/staticaml # Deploy to these servers. servers: web: - 123.456.78.987 # Set ssl: false if using something like Cloudflare to terminate SSL (but keep host!). proxy: ssl: false host: my-static-site.com # Credentials for your image host. # Default to using docker hub. registry: username: matthewroach password: - KAMAL_REGISTRY_PASSWORD # Configure builder setup. builder: arch: arm64 # Inject ENV variables into containers (secrets come from .kamal/secrets). env: secret: - KAMAL_REGISTRY_PASSWORD </code></pre> <p>For the environment variable <code>KAMAL_REGISTRY_PASSWORD</code> we need this to be set as an environment variable. To do this we can use <code>.env</code> file to set the secret in. <strong>Make sure you add the file to your gitigore</strong> to avoid having your secret in git.</p> <p>Create a <code>.env</code> file with key <code>KAMAL_REGISTRY_PASSWORD</code> and the value being your docker hub personal access token</p> <pre><code class="language-bash">KAMAL_REGISTRY_PASSWORD=&quot;{DOCKER PERSONAL ACCESS TOKEN}&quot; </code></pre> <p>To get the environment variable available to Kamal config we need to update the <code>config/deploy.yml</code> file to load the environment file using <code>dotenv</code> - add the following to the first line of <code>config/deploy.yml</code></p> <pre><code class="language-yaml">&lt;% require &quot;dotenv&quot;; Dotenv.load(&quot;.env&quot;) %&gt; </code></pre> <p>Be sure to commit all your changes, and then we are ready to deploy our site</p> <h3>Kamal Health Check</h3> <p>As part of the Kamal tooling it requires a health check endpoint (which can be configured), by default it uses <code>/up</code> URL of your site to check if things are health.</p> <p>Be sure to add a route to your static site. Simply add an <code>index.html</code> file that is served at <code>my-static-site.com/up</code></p> <h2>Deploying Site</h2> <p>For the first deployment we run <code>kamal setup</code> from the root of our repository. Kamal will do its thing now, running through a number of docker steps, and setting up your server with a proxy and your static site.</p> <p>For following deployments you'll need to make changes and be sure to commit them with git, and then you can run <code>kamal deploy</code>.</p> <p>My demo site deployed using Kamal - <a href="https://staticaml.tealeaves.dev/">https://staticaml.tealeaves.dev</a>.</p> <hr /> <p>I've already used the same steps on a number of my other static sites to deploy them, and with Kamal 2 I am able to deploy them all to a single server.</p> 2021 2021-12-31T00:00:00Z https://matthewroach.me/2021/ <blockquote> <p>Well, a global pandemic</p> </blockquote> <p>Not much changed from <a href="https://matthewroach.me/2020">2020</a>. At times it felt like things were getting better, but as we all know that was short lived. Three vacinations received in 2021, how many will there be in 2022?</p> <p>Some items that happened in what felt like a blur of a year, in no particular order -</p> <ul> <li>πŸ‘¨β€πŸ’» Started off the year by switching jobs after the move last year not working out as I expected</li> <li>🚴 Bought a new road bike and enjoyed the early spring sun, even got Pamela out on the open roads a couple of times, even when there was no cake involved</li> <li>πŸ– Enjoyed a family holiday to Edinburgh that included my parents</li> <li>🚘 Switched to an electric car, first a Vauxhall Corsa-e, then to an Volkswagen ID.3</li> <li>πŸŽ‰ Now a parent to a teenager</li> <li>πŸ‘ My son started playing field hockey for the men’s 2nd team</li> <li>πŸ‘ I started playing hockey again, after playing it when I was younger (20+ years ago). Been amazing being able to play alongside my son, and watch how fast he is progressing.</li> <li>πŸ‘©β€πŸŽ“ My wife finished her studies and started a new job with her qualifications</li> </ul> <p>2022 - who knows what’s going to happen…</p> A Themeable Design System 2021-12-09T00:00:00Z https://matthewroach.me/a-themeable-design-system/ <p>I've been involved with design systems for a good number of years. Even before design systems, there where styleguides. Remember those?</p> <p>Design Systems, Styleguides, pattern libraries, etc. They are all great tools/systems and having created and consumed a variety of these systems over the years I've recently come up against a problem where the &quot;traditional&quot; systems where not flexible enough.</p> <p>If you go look at any of the big tech companies desgin system(s) they have published you'll notice they are all aimed at the companies usage, which is great. If you are building an integration on top of shopify using their design system polaris to maintain consistency from your integration to shopify's application is great.</p> <p>Even some big names provide &quot;themes&quot; on top of their systems. If the company has a few identities you are able to use the correct theme for the relevant company.</p> <p>These are all great, but -</p> <p><strong>What if you wanted to provide as much flexibility as possible?</strong></p> <p><strong>What if you didn't know who was going to using the system?</strong></p> <p><strong>What if you wanted the ability for the system to be flexible enough to apply a companies own style to it?</strong></p> <p>These are just a few of the questions I was faced with answering recently. How can we provide a core set of components, that are ultimately fully customisable at the core? But also provide an &quot;out-of-the-box&quot; theme.</p> <p>Based on these questions and more, I came up with following solution which I am going to walk you through.</p> <h2>Technology</h2> <p>For the solution as it is to be used in part of an exisiting ecosystem. The technology stack wasn't completely open. React was a hard requirment, the styling was a bit more open - it was either going to be a CSS-in-JS approach or CSS. I opted for CSS, but with Sass as the integration layer, and the end result being a standalone CSS file.</p> <h2>System Overview</h2> <p>The system is broken down to three parts</p> <ul> <li>Theme</li> <li>Components</li> <li>Blocks</li> </ul> <p>The core of the system is a set of <strong>components</strong>, think of these as the core. The <strong>components</strong> would be used as the building elements of <strong>blocks</strong>, example of such components -</p> <ul> <li>Paragraph</li> <li>Image</li> <li>Caption</li> <li>Date</li> <li>Link</li> </ul> <p>These <strong>components</strong> are very presentational, and are a means to provide a consistent interface to commonly used <strong>components</strong> of the system. The <strong>components</strong> are not limited to outputting text, images, etc, a <strong>component</strong> can also be a layout component.</p> <p><strong>Blocks</strong> are used within the system to provide an entry point and exit point. The entry point being the internal system, and the exit point being the published page. A <strong>block</strong> is a composed component of functionality that is made up primarily of <strong>components</strong>. An example of a <strong>block</strong> would be</p> <ul> <li>Promo Item</li> <li>List of articles</li> <li>Search Results list</li> </ul> <p><strong>Theme</strong> the theme is what brings it all together and holds the styling information. The theme is a collection of Sass maps that hold the CSS properties and values for a <strong>component</strong>, a <strong>block</strong> and also the ability for <strong>component</strong> within a <strong>block</strong> to be styled.</p> <h2>System Breakdown</h2> <h3>Components</h3> <p>To understand how it all fits together and what is involved at each level, I am going to run through a basic example.</p> <p>The paragraph <strong>component</strong> looks as follows</p> <pre><code class="language-javascript">import React from &quot;react&quot;; import PropTypes from &quot;prop-types&quot;; const Paragraph = ({ children }) =&gt; &lt;p className=&quot;c-paragraph&quot;&gt;{children}&lt;/p&gt;; Paragraph.propTypes = { children: PropTypes.string.isRequired, }; export default Paragraph; </code></pre> <p>Very simple and basic React component that takes children and output wraps that in a <code>p</code> tag, where we apply a class of <code>c-paragraph</code>.</p> <p>The class applied to the component is exposed via a Sass file which looks as follows</p> <pre><code class="language-css">.c-paragraph { @include component-properties(&quot;paragraph&quot;); } </code></pre> <p>This would the bare minimum for a component Sass file. It looks a little bare, and not doing anything at the moment. This is because the styling will come from the <strong>theme</strong>. We are using a mixin provided by the system that would output the CSS properties and values for a <code>paragraph</code></p> <h3>Blocks</h3> <p>As mentioned earlier, <strong>blocks</strong> are composed of <strong>components</strong>. An very simple <strong>block</strong> could look like</p> <pre><code class="language-javascript">import React from &quot;react&quot;; import PropTypes from &quot;prop-types&quot;; import { Paragraph } from &quot;components&quot;; const FakeStory = { description: &quot;One of the highest paved roads in Europe, this mountain pass makes for a thrilling road trip into the misty mountains above the Adriatic and Ionian seas.&quot;, }; const Promo = () =&gt; ( &lt;div className=&quot;b-promo&quot;&gt; &lt;Paragraph&gt;{FakeStory.description}&lt;/Paragraph&gt; &lt;/div&gt; ); export default Promo; </code></pre> <p>As you can see it's very basic. The promo <strong>block</strong> is only using a single <strong>component</strong> in this example, but it could use any number of the core <strong>components</strong> if needed. The <strong>block</strong> has a wrapping <code>div</code> with a class <code>b-promo</code> this is then exposed via a <strong>block</strong> Sass file to allow styling of the <strong>block</strong> but also <strong>components</strong> inside a block.</p> <p>The Sass file for a block is as follows</p> <pre><code class="language-css">.b-promo { @include block-components(&quot;promo&quot;); @include block-properties(&quot;promo&quot;); } </code></pre> <p>Similar to <strong>components</strong>, we are making use of mixins to load in the <code>promo</code> block styles, but also load in the styles for <strong>components</strong> within a <strong>block</strong>. How the <strong>component</strong> styles work will be explained below as part of the <strong>theme</strong>.</p> <h3>Theme</h3> <p>The <strong>theme</strong> is where the styling is controlled for a <strong>component</strong>, a <strong>block</strong> and for <strong>components</strong> within a <strong>block</strong>.</p> <p>The <strong>theme</strong> is utilising Sass to extend the capabilites of CSS by providing some mixins and better variable management. The output from the Sass files is a CSS file that is utilising CSS Variables. The primary reason to use Sass is the power it provides with being able to doing looping and provide functions (mixins).</p> <p>Within the <strong>theme</strong> there are multiple Sass maps, following the design token approach it's broken down into four distinct areas</p> <ul> <li>Global</li> <li>Alias</li> <li>Components</li> <li>Blocks</li> </ul> <h4>Global</h4> <p>Global tokens are sets of tokens that are the core to the system. Color pallettes, font sizing, font weight, spacing, etc. Each token in the global tokens map is output as a CSS Variable prefixed with <code>--global-</code>.</p> <pre><code class="language-css">$global: ( &quot;gray-50&quot;: rgb(218 218 218) ); </code></pre> <p>is then converted into the following CSS</p> <pre><code class="language-css">:root { --global-gray-50: rgb(218 218 218); } </code></pre> <h4>Alias</h4> <p>Alias tokens are technically still global tokens, but provide the ability to have a friendly name attached to a given global token. For example, <code>primary-color</code> would be an alias token, and the value of that token would reference a global token. Each token in the alias map is output as a CSS variable - these are not prefixed at all.</p> <pre><code class="language-css">$alias: ( &quot;primary-color&quot;: rgb(100 100 100) ); </code></pre> <p>is then converted into the following CSS</p> <pre><code class="language-css">:root { --primary-color: rgb(100 100 100); } </code></pre> <h4>Components</h4> <p>Component and block tokens are a little different to the global and alias set up, as these are nested Sass maps that follow the CSS property, and value set up.</p> <p>For components the nested Sass map is structured with the first key inside the map being a <strong>component</strong> name. If you remember from before there is a Sass mix that looks up the <strong>component</strong> tokens by name. Meaning the <strong>component</strong> name will need to match that which is set in the components .scss file.</p> <pre><code class="language-css">$tokens: ( components: ( &quot;paragraph&quot;: () ) ); </code></pre> <p>Then inside the <code>paragraph</code> map we have the ability to use CSS properties and values just as if you were writing CSS, allowing you do something like</p> <pre><code class="language-css">'paragraph': ( 'font-size': 16px, 'font-weight': bold, ), </code></pre> <p>If we bring back the code from above of how we write the Sass for the paragraph component we can see how they work together</p> <pre><code class="language-css">.c-paragraph { @include component-properties(&quot;paragraph&quot;); } </code></pre> <p>With the above code and the above example the generated CSS would be</p> <pre><code class="language-css">.c-paragraph { font-size: 16px; font-weight: bold; } </code></pre> <p>Obviously that's not great to use hard coded values as the value items in these maps as you loose the power of the token system. Knowing how the previous global and alias tokens are converted you can reference those as the values</p> <pre><code class="language-css">'paragraph': ( 'font-size': var(--global-font-size-200), 'font-weight': var(--global-font-weight-700), ), </code></pre> <p>this would then make use of the CSS variable in the outputting CSS as follows</p> <pre><code class="language-css">:root { --c-paragraph-font-size: var(--global-font-size-200); --c-paragraph-font-weight: var(--global-font-weight-700); } .c-paragraph { font-size: var(--c-paragraph-font-size); font-weight: var(--c-paragraph-font-weight); } </code></pre> <h4>Blocks</h4> <p>The blocks part of the theme works in a similar way to components the <code>@include block-properties('promo');</code> Sass mixing does pretty much the same as the <code>component-properties</code> version, the only difference is it's looking for the named item within the <code>blocks</code> map.</p> <pre><code class="language-css">$tokens: ( blocks: ( &quot;promo&quot;: () ) ); </code></pre> <p>The added ability you have within a block is the ability to change the style of a <strong>component</strong> differently to that of the base <code>components</code> styling. As we mentioned before <strong>blocks</strong> are composed of <strong>components</strong>, this means by default and using the CSS cascade all components will have the same styling when used within a <strong>block</strong>. But what if you want to make the <code>paragraph</code> component within the <code>promo</code> block to be styled slightly different? Say we wanted to increase the <code>font-size</code> thats where the Sass mixin <code>@include block-components('promo');</code> comes in.</p> <p>Given we have the need to update the <code>paragraph</code> font size within a <code>promo</code> block the blocks map has the ability for us to define <strong>component</strong> styles for a given block.</p> <pre><code class="language-css">$tokens: ( blocks: ( 'promo: ( 'components': ( 'paragraph': ( 'font-size': 18px, ), ), ), ), ); </code></pre> <p>To break down the nested map it can be a little easier read from the inside out. What the map is saying is for the <code>paragraph</code> component inside a <code>block</code> set the <code>font-size</code> to <code>18px</code>. How this is translated to CSS is by using the power of CSS variables, as previously mentioned we are outputting to CSS variables at all times meaning that we are able redfined the paragraph font size CSS variable within the promo block CSS.</p> <p>Reminder of how the block Sass file is</p> <pre><code class="language-css">.b-promo { @include block-components(&quot;promo&quot;); @include block-properties(&quot;promo&quot;); } </code></pre> <p>Based on the above example of setting the <code>paragraph</code> <code>font-size</code> to <code>18px</code> within a <code>promo</code> block the CSS would be output as</p> <pre><code class="language-css">.b-promo { --c-paragraph-font-size: 18px; } </code></pre> <h2>Working Examples</h2> <p>Code is available on GitHub - <a href="https://github.com/matthewroach/theme-components">Theme Component Repo</a></p> <p>It's all demo'd via storybook which is published - <a href="https://theme-components.notio.xyz/?path=/story/blocks-promo--default-story">Theme Components Storybook</a></p> JAMStack URL Shortener with Netlify, 11ty and GitHub 2021-02-07T00:00:00Z https://matthewroach.me/jamstack-url-shortener-with-netlify-11ty-and-github/ <h2>Why...</h2> <p>I wanted a place where I can &quot;bookmark&quot; things I come across on the internet. Either for reading later, sharing or to keep a record of it for future reference.</p> <p>For the sharing part I wanted to a way to use a shortened style link</p> <p>Also, I want to own all this data. Along with not having to maintain a web application.</p> <p><strong>What if I could do all this with static site generation tool 11ty, and Netlify?</strong></p> <h2>How...</h2> <p>Well turns out it's very possible, and much easier than you might think.</p> <p>Broken down into the following parts</p> <ul> <li>Netlify to host the site and handle the URL redirection</li> <li>Eleventy to handle the website output and redirect file</li> <li>GitHub issues as a UI to add items</li> <li>GitHub actions to handle adding the content from the issue to the site</li> </ul> <p>And the output of all this is my own URL shortner/bookmark &quot;application&quot; - https://roach.link</p> <h2>Netlfiy</h2> <p>For the Netlify part it's very straight forward. The provide a way to host a site, and also a mechanism for handling redirects. Setting up a site is pretty straight forward from their UI with a few customisable options. For the <a href="https://www.netlify.com/blog/2019/01/16/redirect-rules-for-all-how-to-configure-redirects-for-your-static-site/">redirect</a> part as long as you have a <code>_redirects</code> file in the directory you want to serve your site from they take care of the rest. The also allow you have use a <code>netlify.toml</code> file.</p> <h2>Eleventy (11ty)</h2> <p>For the <a href="https://www.11ty.dev/">11ty</a> part I went for a very similar set up to my personal site (this site) set up. Each link would be a markdown file inside a <code>posts</code> directory that looks like this</p> <pre><code class="language-markdown">--- title: &quot;Matthew Roach&quot; date: &quot;2021-01-24&quot; permalink: &quot;b/view/&quot; link: &quot;https://matthewroach.me&quot; tags: [&quot;personal&quot;, &quot;blog&quot;] --- Personal website of Matthew Roach </code></pre> <p>The frontmatter data is where the majority of the needed data is housed. You may notice the <code>permalink</code> seems a little odd why is it <code>b/view</code>? The <code>b</code> part is the URL shortner unique ID, and the view part is used to build the output for each post to be <code>roach.link/b/view</code> allowing you to view the individual bookmark item (single view page still work in progress).</p> <p>Hang on.. You said unique ID... That's going to be fun to maintain I hear you ask. Well it would be if you were trying to do it by hand. But I am not, I am using an <code>npm</code> package called <a href="https://www.npmjs.com/package/bijective-link-shortener">bijective-link-shortener</a> to increment them each time. Which is handled with GitHub as you'll see later.</p> <h3>Redirect file</h3> <p>Without the ability to do redirection a URL shortner isn't much use, and as previously mentioned Netlfiy allows you to do redirects if you provide either a <code>_redirects</code> or <code>netlify.toml</code> file in the root of the published site. And with 11ty this is made super simple to generate, as with 11ty you can configure it to output the input file to any output file (give or take), by setting the <code>permalink</code> to be the output.</p> <p>Given all the bookmarks are all going to be individual files in a folder I set up a new collection in the <code>.eleventy.js</code> config file named <code>allLinks</code> and reversed the collection so the newest would come first.</p> <p>With a file named <code>_redirects.11ty.js</code> in my <code>src</code> directory I can consume the <code>allLinks</code> collection and make 11ty render out the links in a format needed for Netlify. The <code>_redirects.11ty.js</code> looks as follows</p> <pre><code class="language-javascript">class Redirects { data() { return { permalink: &quot;netlify.toml&quot;, }; } render(data) { return data.collections.allLinks .map( (l) =&gt; ` [[redirects]] from = &quot;${l.url.split(&quot;/&quot;)[1]}&quot; // permalink is in format {shortLink}/view to = &quot;${l.data.link}&quot;` ) .join(&quot;&quot;); } } module.exports = Redirects; </code></pre> <p>Broken down the file is looping over the <code>allLinks</code> collection, and setting up the Netlify redirect file in the format they require by using the permalink data (first part) and the link from each posts frontmatter data. As as the <code>permalink</code> of this file is set to <code>netlify.toml</code> it will create that file based on the <code>render</code> method.</p> <p>The output file looks as follows</p> <pre><code>[[redirects]] from = &quot;b&quot; to = &quot;https://matthewroach.me&quot; </code></pre> <h2>GitHub</h2> <p>GitHub is where I am hosted the source code and then have Netlify linked to the repo to build the site and publish on each commit to the <code>main</code> branch.</p> <h3>Issues</h3> <p>But GitHub is also the primary way I add new data to the site. Rather than having to create a new file and fill in all the frontmater data, and do a host of git commands I wanted a way I could actually use this &quot;application&quot; with minimal effort. Turns out GitHub issues is a <s>great</s> perfectly acceptable way to be the UI for adding new bookmarks... That is if you are willing to make a small adjustment.</p> <p>So, GitHub issues provides all but one of the interface items I needed. This is how I mapped the GitHub issue UI to the frontmatter data</p> <ul> <li>Issue Comment = The post body</li> <li>Labels = Tags</li> <li>Title = Title and link</li> </ul> <p>Title = title and link? That seems odd. Well turns out the issue UI doesn't really have a field or place I could enter the URL for the item I want to bookmark. So I went with the format of making the title as follows:</p> <pre><code>Matthew Roach Blog::https://matthewroach.me </code></pre> <p>The two colons has no meaning other than for when the GitHub action runs and splits the title using <code>title.split('::')</code> to parse it into two parts. The first part is the Title of the Site, and the second is then used in the link frontmatter data field.</p> <h3>Actions</h3> <p><a href="https://github.com/features/actions">GitHub actions</a> is the glue that makes all the previous steps work together. Without actions the process of adding bookmarks would be very manual. Actions are a way for you to automate workflows from your GitHub repository. Based on an action you can configure an action to run and do &quot;things&quot;. Those &quot;things&quot; are what makes this all possible.</p> <p>As mentioned I am using GitHub issues as a UI to add new bookmarks. Broken down this works as follows</p> <ol> <li>Issue <ol> <li>Add a new issue to my roach.link repository</li> <li>Once I am happy with the description and tags</li> <li>Mark the issue as closed</li> </ol> </li> <li>GitHub action runs on issue closed event <ol> <li>The action is triggered and starts its &quot;steps&quot;</li> <li>Action checks out code, sets up node and makes the issue object available on an environment variable</li> <li>Then a node command runs a script <code>node new-item-from-issue.js</code> 1. Within the script I have access to the issue object by doing <code>JSON.parse(process.env.ISSUE_CONTEXT)</code> 2. Script checks how many posts are currently in the <code>posts</code> folder, and using the package <code>bijective-link-shortener</code> gets the next short link value 3. Using the data from the issue object and the short link value from the previous step I use <code>transformAndWriteToFile</code> from the package <code>json-to-frontmatter-markdown</code> to create a new markdown file in the format noted above</li> <li>Add and commit is then run from within the action to add the newly created file to the repo</li> </ol> </li> <li>Netlify is triggered due to a new commit on the <code>main</code> branch <ol> <li>Netlify runs its build steps I have configured</li> </ol> </li> <li>New version of site is live at <a href="https://roach.link/">https://roach.link</a> <ol> <li>Homepage is updated</li> <li>New redirect added - example: <a href="https://roach.link/b">https://roach.link/b</a></li> </ol> </li> </ol> VSCode focus between terminal and code 2021-01-30T00:00:00Z https://matthewroach.me/vscode-focus-between-terminal-and-code/ <p> I recently began to utilise the integrated terminal for VSCode more. And being a big fan of using my keyboard to switch around different parts is huge part of my work flows. One of my sticking points with the terminal in VSCode is by default you can not switch into and out of the terminal easily. </p> <p> For example if I am editing some code, then need to switch to the terminal to run some commands, and then switch back to the code window. I got so used to using command + tab to switch windows. Wanting to try and use VSCode more it turns out this was a sticking point of my flow. With some digging and trial and error it turns out you can setting up a custom keybinding to handle this. </p> <p> As there was already a default key binding to open a terminal, once opened you can not easily switch back. But with VSCode and key bindings you are able to use the <code>when</code> key to tell a key binding "when" something can be run. This allowed me to set the same key binding used to focus terminal as to focus back to the editor. Below is instructions and the key binding JSON to achieve this on a Mac. </p> <h2>Instructions for Mac</h2> <ol> <li>Code > Preferences > Keyboard Shortcuts</li> <li> This opens a GUI for keyboard shortcuts - click on the tab "Keyboard Shortcuts", and an icon with a file and arrow will appear (title - Open Keyboard Shortcuts JSON) </li> <li>Copy paste the below JSON into the file and save</li> <li>Done</li> </ol> <pre><code>[ { "key": "ctrl+`", "command": "workbench.action.terminal.focus" }, { "key": "ctrl+`", "command": "workbench.action.focusActiveEditorGroup", "when": "terminalFocus" } ]</code></pre> <hr /> <p> Another VSCode tip - <a href="https://matthewroach.me/vscode-zoom-only-font-not-whole-editor/">VSCode zoom only font not whole editor</a>. </p>