<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Precious Chicken</title>
    <description>The latest articles on DEV Community by Precious Chicken (@preciouschicken).</description>
    <link>https://dev.to/preciouschicken</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%2F189422%2Fb92b0069-0f03-4cc5-9c2c-4c4ae2a6fed9.png</url>
      <title>DEV Community: Precious Chicken</title>
      <link>https://dev.to/preciouschicken</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/preciouschicken"/>
    <language>en</language>
    <item>
      <title>Githubhack23: Vegaration or Visualising continuous integration using Github actions and vega-lite</title>
      <dc:creator>Precious Chicken</dc:creator>
      <pubDate>Mon, 22 May 2023 23:14:15 +0000</pubDate>
      <link>https://dev.to/preciouschicken/githubhack23-vegaration-or-visualising-continuous-integration-using-github-actions-and-vega-lite-48c9</link>
      <guid>https://dev.to/preciouschicken/githubhack23-vegaration-or-visualising-continuous-integration-using-github-actions-and-vega-lite-48c9</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;vegaration (noun) [vay-gah-grey-shuhn]: The use of &lt;strong&gt;vega&lt;/strong&gt;-lite to visualise continuous integ-&lt;strong&gt;ration&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;A tool to aid development teams by visualising Continuous Improvement.  I used GitHub actions to log information about every commit made on a repo (e.g. user, sha, passing tests, failing tests) into a json file stored within the repo.  This file was then used as the source for a number of visualisations implemented with the vega-lite specification.  The same GitHub action workflow then published these visualisations to GitHub Pages so they could be viewed by all stakeholders.&lt;/p&gt;

&lt;p&gt;This demonstration proves the possibility of logging and visualising a whole suite of Continuous Improvement metrics which could help teams identify patterns, address problems and increase efficiency.  It differs from other CI visualisations (e.g. Jenkins) in the flexibility it offers: &lt;a href="https://vega.github.io/vega-lite/"&gt;vega-lite is a data science tool&lt;/a&gt; meaning it offers a very wide range of visualisation possibilities; likewise logging via GitHub actions allows you the freedom to capture almost anything about the CI/CD process that your team is interested in.&lt;/p&gt;

&lt;h3&gt;
  
  
  Category Submission:
&lt;/h3&gt;

&lt;p&gt;DIY Deployments&lt;/p&gt;

&lt;h3&gt;
  
  
  App Link
&lt;/h3&gt;

&lt;p&gt;Continuous Integration Visualisation on GitHub Pages:&lt;br&gt;
&lt;a href="https://preciouschicken.github.io/vegaration/"&gt;https://preciouschicken.github.io/vegaration/&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Screenshots
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.preciouschicken.com/blog/images/vegaration-visualisation.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--p3exW5Qd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.preciouschicken.com/blog/images/vegaration-visualisation-thumb.png" alt="Continuous integration visualisation" width="630" height="779"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Description
&lt;/h3&gt;

&lt;p&gt;Visualising continuous integration using GitHub actions and vega-lite, natively hosted on GitHub Pages. &lt;/p&gt;

&lt;h3&gt;
  
  
  Link to Source Code
&lt;/h3&gt;

&lt;p&gt;GitHub repository:&lt;br&gt;
&lt;a href="https://github.com/PreciousChicken/vegaration"&gt;https://github.com/PreciousChicken/vegaration&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Permissive License
&lt;/h3&gt;

&lt;p&gt;MIT&lt;/p&gt;

&lt;h2&gt;
  
  
  Background (What made you decide to build this particular app? What inspired you?)
&lt;/h2&gt;

&lt;p&gt;The integration of GitHub Actions and Vega-Lite to visualise continuous integration for software development offers a highly useful and beneficial approach for several reasons. &lt;/p&gt;

&lt;p&gt;Firstly, it allows developers to gain a comprehensive and real-time understanding of the continuous integration process by providing visual representations of various metrics. These visualisations make it easier to identify patterns, trends, and potential issues at a glance, enabling faster and more efficient decision-making.  This then enhances the ability of a team to track progress, evaluate the impact of changes, and identify areas for improvement. &lt;/p&gt;

&lt;p&gt;Secondly, by integrating the visualisation directly into the software development workflow, teams can foster a culture of transparency and collaboration. Developers, project managers, and stakeholders can all access and interpret the visualisations, enabling a shared understanding and facilitating communication. &lt;/p&gt;

&lt;p&gt;Ultimately, the combination of GitHub Actions and vega-Lite empowers software development teams to make data-driven decisions, streamline processes, and ensure the delivery of high-quality software.&lt;/p&gt;

&lt;h3&gt;
  
  
  How I built it (How did you utilize GitHub Actions or GitHub Codespaces? Did you learn something new along the way? Pick up a new skill?)
&lt;/h3&gt;

&lt;p&gt;I utilised GitHub actions, GitHub Pages and the vega-lite visualisation specification.  I learnt lots about all three in the process!  I did consider using vega rather than vega-lite, but after trying both out decided for the higher-level grammar.&lt;/p&gt;

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

&lt;p&gt;Worked example / Step-by-step guide:&lt;br&gt;
&lt;a href="https://dev.to/preciouschicken/vegaration-visualising-continuous-integration-using-github-actions-and-vega-lite-a97"&gt;https://dev.to/preciouschicken/vegaration-visualising-continuous-integration-using-github-actions-and-vega-lite-a97&lt;/a&gt;&lt;/p&gt;

</description>
      <category>githubhack23</category>
      <category>vegalite</category>
    </item>
    <item>
      <title>Vegaration: Visualising continuous integration using Github actions and vega-lite</title>
      <dc:creator>Precious Chicken</dc:creator>
      <pubDate>Mon, 22 May 2023 17:50:14 +0000</pubDate>
      <link>https://dev.to/preciouschicken/vegaration-visualising-continuous-integration-using-github-actions-and-vega-lite-a97</link>
      <guid>https://dev.to/preciouschicken/vegaration-visualising-continuous-integration-using-github-actions-and-vega-lite-a97</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;vegaration (noun) [vay-gah-grey-shuhn]: The use of &lt;strong&gt;vega&lt;/strong&gt;-lite to visualise continuous integ-&lt;strong&gt;ration&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a worked example for Linux which aims to visualise the software development practice of continuous integration (CI).  It uses &lt;a href="https://github.com/features/actions"&gt;GitHub actions&lt;/a&gt; to manage the CI workflow and &lt;a href="https://vega.github.io/vega-lite/"&gt;vega-lite&lt;/a&gt; as the visualisation specification.  Or to put it simply: every time someone commits to a repository on GitHub we can log elements associated with their commit (e.g. what tests passed, what tests failed) and then present this data visually using a wide suite of open-source metrics.&lt;/p&gt;

&lt;p&gt;This demonstration proves the possibility of logging and visualising a whole suite of Continuous Improvement metrics which could help teams identify patterns, address problems and increase efficiency. The example created here, which can be viewed on the &lt;a href="https://preciouschicken.github.io/vegaration/"&gt;Vegaration GitHub Page&lt;/a&gt;, offers a very small slice of the total possibilities on offer.  It differs from other CI visualisations (e.g. Jenkins) in the flexibility it offers: vega-lite is a data science tool meaning it offers a very wide range of visualisation possibilities; likewise logging via GitHub actions allows you the freedom to capture almost anything about the CI/CD process that your team is interested in.&lt;/p&gt;

&lt;p&gt;All code is contained in the repo &lt;a href="https://github.com/PreciousChicken/vegaration"&gt;PreciousChicken/vegaration&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This was written using Manjaro Linux 22.1.1, node v16.13.2, vega-lite 5.9.1 and npm v8.3.2.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the GitHub repo
&lt;/h2&gt;

&lt;p&gt;The first step is creating a new repo on GitHub as demonstrated:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.preciouschicken.com/blog/images/vegaration-new-repo.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_V_AEE_0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.preciouschicken.com/blog/images/vegaration-new-repo-thumb.png" alt="Create a new github repo" width="630" height="700"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ensure as you do so you set set the &lt;strong&gt;&lt;em&gt;.gitignore&lt;/em&gt;&lt;/strong&gt; option to &lt;code&gt;Node&lt;/code&gt;.  Readme is of course optional.&lt;/p&gt;

&lt;h2&gt;
  
  
  Clone into the repo
&lt;/h2&gt;

&lt;p&gt;At the terminal clone into the repo using the following command - replacing the repo with the one created at the step above.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone git@github.com:PreciousChicken/vegaration.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then change directory into the repo you have created, e.g.:&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;cd &lt;/span&gt;vegaration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing...
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Installing jest
&lt;/h3&gt;

&lt;p&gt;To demonstrate the continuous integration it is necessary to create some mock tests.  The &lt;a href="https://jestjs.io/"&gt;Jest&lt;/a&gt; framework will be our testing weapon of choice, so at the command line enter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i jest &lt;span class="nt"&gt;--save-dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will install the jest package, but we now need to add the test script to our &lt;strong&gt;&lt;em&gt;package.json&lt;/em&gt;&lt;/strong&gt; file which should be amended to look like this (the jest version will naturally be different depending on whatever is current):&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;"devDependencies"&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;"jest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^29.5.0"&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;"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;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"jest"&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;h3&gt;
  
  
  The tests
&lt;/h3&gt;

&lt;p&gt;Now to create some simple functions and associated tests, nothing too complex.  Create a file called &lt;strong&gt;&lt;em&gt;add.js&lt;/em&gt;&lt;/strong&gt; and paste the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then create the associated test file &lt;strong&gt;&lt;em&gt;add.test.js&lt;/em&gt;&lt;/strong&gt; and paste:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;adds 1 + 1 to equal 2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sum&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;1&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&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;We may as well do subtraction too, so create a file called &lt;strong&gt;&lt;em&gt;minus.js&lt;/em&gt;&lt;/strong&gt; and paste:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;subtract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;subtract&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the associated test file &lt;strong&gt;&lt;em&gt;minus.test.js&lt;/em&gt;&lt;/strong&gt; with the following:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;substract 2 - 1 to equal 1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subtract&lt;/span&gt;&lt;span class="p"&gt;(&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;1&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBe&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That now gives us two functions and two tests.  Try them out by entering &lt;code&gt;npm test&lt;/code&gt; at the terminal, and assuming copy and paste worked out, then jest should recognise the test files and both tests should pass with flying colours.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hosting the visualisation on GitHub pages
&lt;/h2&gt;

&lt;p&gt;Our visualisation has to be hosted somewhere - and it makes sense to use GitHub pages.  To make things not so bare-bones we are going to ornament these with the &lt;a href="https://github.com/pages-themes/dinky"&gt;Dinky theme&lt;/a&gt;, but it makes little difference.&lt;/p&gt;

&lt;h3&gt;
  
  
  Base
&lt;/h3&gt;

&lt;p&gt;Create a folder to base the GitHub pages and then switch to it:&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;visualisation &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;visualisation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Typically this folder gets called &lt;strong&gt;&lt;em&gt;Docs&lt;/em&gt;&lt;/strong&gt;, but being descriptive is never a bad thing.&lt;/p&gt;

&lt;p&gt;Create a configuration file for the GitHub pages named &lt;strong&gt;&lt;em&gt;_config.yml&lt;/em&gt;&lt;/strong&gt; (yes, it does start with an underscore) and paste:&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;theme&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jekyll-theme-dinky&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Vegaration&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Visualising continuous integration using Github actions and vega-lite&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Vega-liteify
&lt;/h3&gt;

&lt;p&gt;To allow vega-lite visualisations to be embedded the GitHub pages need to know where to look to understand the visualisation, so create a new folder (again starting with an underscore) within the &lt;strong&gt;&lt;em&gt;visualisations&lt;/em&gt;&lt;/strong&gt; folder and switch to it:&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;_includes &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;_includes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then create the file &lt;strong&gt;&lt;em&gt;head-custom.html&lt;/em&gt;&lt;/strong&gt; and paste:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.jsdelivr.net/npm/vega@5"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.jsdelivr.net/npm/vega-lite@5"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.jsdelivr.net/npm/vega-embed@6"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This snippet correspondingly gets picked up by the theme and entered within the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; section of the finalised HTML (if we weren't using a theme we would place it directly in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;, but given we are using Dinky this is much neater).&lt;/p&gt;

&lt;h3&gt;
  
  
  The visualisation
&lt;/h3&gt;

&lt;p&gt;Returning to our &lt;strong&gt;&lt;em&gt;visualisation&lt;/em&gt;&lt;/strong&gt; folder:&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;cd&lt;/span&gt; ..
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we are going to create the file which will actually craft our visualisation, which is a mix of markdown and HTML as allowed by Jekyll (the engine which runs GitHub pages).  This is where we will place the vega-lite specification which will create the embedded charts.  This worked example is not going to dive into the vega-lite specification itself, for that you are probably best starting at the &lt;a href="https://vega.github.io/vega-lite/tutorials/getting_started.html"&gt;vega-lite tutorial&lt;/a&gt;.  That said create the file &lt;strong&gt;&lt;em&gt;index.md&lt;/em&gt;&lt;/strong&gt; and paste:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Continuous Integration Visualisation&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gu"&gt;## Commits per user&lt;/span&gt;

Bar chart showing commits per user:

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"commitsPerUser"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/javascript"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    var commitsPerUser = {
        "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
        "description": "Bar chart showing commits per user.",
        "config": {
        "background": "#FBFAF7"
        },
        "width": 300,
        "data": {"url": "./log.json"},
        "transform": [
        {
        "aggregate": [{
        "op": "count",
        "field": "sha",
        "as": "Commits"
        }],
        "groupby": ["user"]
        }
        ],
        "mark": "bar",
        "encoding": {
        "x": {"field": "Commits", "type": "quantitative",
        "axis": {"tickMinStep": 1}
        },
        "y": {"field": "user", "type": "nominal", "sort": "descending"}
        }
        };
    vegaEmbed('#commitsPerUser',commitsPerUser);
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="gu"&gt;## Passing vs Failing Tests&lt;/span&gt;

&lt;span class="gu"&gt;### add.test.js&lt;/span&gt;

Pie chart showing fails vs passes for add.test.js (hover over segment for tooltip display):

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"addPie"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/javascript"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    var addPie = {
        "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
        "data": {"url": "./log.json"},
        "description": "Pie chart showing pass vs fail for add_test_js",
        "config": {
        "background": "#FBFAF7"
        },
        "transform": [
        {"filter": {"field": "add_test_js", "oneOf": ["FAIL", "PASS"]}},
        {"aggregate": [{"op": "count", "as": "count"}], "groupby": ["add_test_js"]}
        ],
        "mark": {
        "type": "arc",
        "tooltip": true
        },
        "encoding": {
        "theta": {"field": "count", "type": "quantitative"},
        "color": {
        "field": "add_test_js",
        "type": "nominal",
        "scale": {
        "domain": ["FAIL", "PASS"],
        "range": ["red", "green"]
        },
        "legend": {
        "title": "Result",
        "labelExpr": "datum.value === 'FAIL' ? 'Fail' : 'Pass'"
        }
        },
        "tooltip": [
        {"field": "count", "type": "quantitative"},
        {"field": "add_test_js", "type": "nominal"}
        ],
        "text": {"field": "count", "type": "quantitative"}
        },
        "width": 300,
        "height": 300
        };
    vegaEmbed('#addPie', addPie);
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;&lt;span class="sb"&gt;


&lt;/span&gt;&lt;span class="gu"&gt;### minus.test.js&lt;/span&gt;

Pie chart showing fails vs passes for minus.test.js (hover over segment for tooltip display):

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"minusPie"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/javascript"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    var minusPie = {
        "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
        "data": {"url": "./log.json"},
        "description": "Pie chart showing pass vs fail for minus_test_js",
        "config": {
        "background": "#FBFAF7"
        },
        "transform": [
        {"filter": {"field": "minus_test_js", "oneOf": ["FAIL", "PASS"]}},
        {"aggregate": [{"op": "count", "as": "count"}], "groupby": ["minus_test_js"]}
        ],
        "mark": {
        "type": "arc",
        "tooltip": true
        },
        "encoding": {
        "theta": {"field": "count", "type": "quantitative"},
        "color": {
        "field": "minus_test_js",
        "type": "nominal",
        "scale": {
        "domain": ["FAIL", "PASS"],
        "range": ["red", "green"]
        },
        "legend": {
        "title": "Result",
        "labelExpr": "datum.value === 'FAIL' ? 'Fail' : 'Pass'"
        }
        },
        "tooltip": [
        {"field": "count", "type": "quantitative"},
        {"field": "minus_test_js", "type": "nominal"}
        ],
        "text": {"field": "count", "type": "quantitative"}
        },
        "width": 300,
        "height": 300
    };
    vegaEmbed('#minusPie', minusPie);
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lastly we need to create a placeholder file where as commits are made data is placed so that it can be visualised; therefore in the same &lt;strong&gt;&lt;em&gt;visualisation&lt;/em&gt;&lt;/strong&gt; folder create the file &lt;strong&gt;&lt;em&gt;log.json&lt;/em&gt;&lt;/strong&gt; and paste:&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="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;
  
  
  Committed
&lt;/h2&gt;

&lt;p&gt;The rest of the steps that need to take place happen on the GitHub site, so we should commit and push the code we have written so far:&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;cd&lt;/span&gt; ..
git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Initial commit"&lt;/span&gt;
git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Action, man
&lt;/h2&gt;

&lt;p&gt;So now we have lined up our tests and the visualisation that shows us information every time they run.  The next step is to link these up with a GitHub action.  This action will run every time there is a commit to the repo and will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run the tests &lt;sup id="fnref1"&gt;1&lt;/sup&gt;
&lt;/li&gt;
&lt;li&gt;Log the results of the test in &lt;strong&gt;&lt;em&gt;log.json&lt;/em&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Publish an updated version of the visualisation on GitHub pages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This workflow however is best created from the GitHub website itself.  Therefore navigate to the Settings on your online repo and then select &lt;strong&gt;&lt;em&gt;Pages&lt;/em&gt;&lt;/strong&gt; from &lt;strong&gt;&lt;em&gt;Code and automation&lt;/em&gt;&lt;/strong&gt;.  Under the heading &lt;strong&gt;&lt;em&gt;Build and deployment&lt;/em&gt;&lt;/strong&gt; select the &lt;strong&gt;&lt;em&gt;GitHub Actions&lt;/em&gt;&lt;/strong&gt; from the &lt;strong&gt;&lt;em&gt;Source&lt;/em&gt;&lt;/strong&gt; dropdown menu and then click on the &lt;strong&gt;&lt;em&gt;create your own&lt;/em&gt;&lt;/strong&gt; link as shown:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.preciouschicken.com/blog/images/vegaration-github-pages.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ssCb5xEm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.preciouschicken.com/blog/images/vegaration-github-pages-thumb.png" alt="GitHub pages settings" width="630" height="237"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You will then be taken to the code editor online, in the box entitled &lt;strong&gt;&lt;em&gt;Name your file...&lt;/em&gt;&lt;/strong&gt; paste the filename &lt;strong&gt;&lt;em&gt;visualisation.yml&lt;/em&gt;&lt;/strong&gt; and then in the main &lt;strong&gt;&lt;em&gt;Edit&lt;/em&gt;&lt;/strong&gt; body of the code editor itself delete the default contents and paste:&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;Visualising continuous integration&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Runs on pushes targeting the default branch&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;main"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="c1"&gt;# Allows you to run this workflow manually from the Actions tab&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# set this to the node version to use if not 16&lt;/span&gt;
  &lt;span class="na"&gt;NODE_VERSION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;16.x'&lt;/span&gt;                

&lt;span class="c1"&gt;# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages&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;contents&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;pages&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;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;

&lt;span class="c1"&gt;# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.&lt;/span&gt;
&lt;span class="c1"&gt;# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.&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="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pages"&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="no"&gt;false&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Saves results of tests&lt;/span&gt;
  &lt;span class="na"&gt;output_test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&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;Set up Node.js&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.NODE_VERSION }}&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;npm'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm install&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run tests and add to log file&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;# Create an shell variable called `tests` and add git commit sha&lt;/span&gt;
          &lt;span class="s"&gt;tests="{\"sha\": \"${{ toJSON(github.sha) }}\", "&lt;/span&gt;
          &lt;span class="s"&gt;# Run npm test and append test results to `tests` variable &lt;/span&gt;
          &lt;span class="s"&gt;# Uses grep to remove all test results apart from FAIL / PASS and test&lt;/span&gt;
          &lt;span class="s"&gt;# Awk used to write to file and replace filename periods with underscores&lt;/span&gt;
          &lt;span class="s"&gt;# Vega-lite does not play well with periods, so add.test.js -&amp;gt; add_test_js&lt;/span&gt;
          &lt;span class="s"&gt;tests+=$(npm test 2&amp;gt;&amp;amp;1 &amp;gt;/dev/null | grep 'FAIL\|PASS' | awk '&lt;/span&gt;
            &lt;span class="s"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;js = gensub(/^\.\/(\w*)\.(\w*)\.(\w*)/, "\\1_\\2_\\3" ,"g", $2) &lt;/span&gt;
            &lt;span class="s"&gt;printf("\"" js"\": \""$1"\", ")&lt;/span&gt;
          &lt;span class="s"&gt;}')&lt;/span&gt;
          &lt;span class="s"&gt;# Appends github user name to `tests` shell variable&lt;/span&gt;
          &lt;span class="s"&gt;tests+="\"user\": \"${{ toJSON(github.actor) }}\"}"&lt;/span&gt;
          &lt;span class="s"&gt;# Shell variable `tests` appended to end of log.json&lt;/span&gt;
          &lt;span class="s"&gt;awk -i inplace -v env_var="$tests" -v RS='^$' -v ORS= '{&lt;/span&gt;
            &lt;span class="s"&gt;print gensub(/\[/,"[\n  "env_var",","g")&lt;/span&gt;
            &lt;span class="s"&gt;}' visualisation/log.json&lt;/span&gt;
          &lt;span class="s"&gt;# Ensure no trailing commas at end of log.json&lt;/span&gt;
          &lt;span class="s"&gt;awk -i inplace -v RS='^$' -v ORS= '{&lt;/span&gt;
            &lt;span class="s"&gt;print gensub(/,\n\]/,"\n]","g")&lt;/span&gt;
          &lt;span class="s"&gt;}' visualisation/log.json&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Push updated log&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;# Should be changed to name of user listed in repo&lt;/span&gt;
          &lt;span class="s"&gt;git config user.name PreciousChicken&lt;/span&gt;
          &lt;span class="s"&gt;# Should be changed to email of user listed in repo&lt;/span&gt;
          &lt;span class="s"&gt;git config user.email hello@preciouschicken.com&lt;/span&gt;
          &lt;span class="s"&gt;git add .&lt;/span&gt;
          &lt;span class="s"&gt;git commit -m "Auto-generated by github actions"&lt;/span&gt;
          &lt;span class="s"&gt;git push&lt;/span&gt;
  &lt;span class="c1"&gt;# Build job&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;output_test&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&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;Setup Pages&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/configure-pages@v3&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build with Jekyll&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/jekyll-build-pages@v1&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;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./visualisation/&lt;/span&gt;
          &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./_site&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload artifact&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-pages-artifact@v1&lt;/span&gt;

  &lt;span class="c1"&gt;# Deployment job&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;environment&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;github-pages&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.deployment.outputs.page_url }}&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;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&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;Deploy to GitHub Pages&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;deployment&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/deploy-pages@v2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are two lines in the above that will need to be changed dependent on the repo you are using. These are: &lt;code&gt;git config user.name PreciousChicken&lt;/code&gt; and &lt;code&gt;git config user.email hello@preciouschicken.com&lt;/code&gt;, change to whatever is appropriate for your repo.  &lt;/p&gt;

&lt;p&gt;Once you have pasted this into the online code editor and made the changes, click the &lt;strong&gt;&lt;em&gt;Commit changes...&lt;/em&gt;&lt;/strong&gt; button and commit directly to the main branch.  At time of writing there is a &lt;a href="https://github.com/orgs/community/discussions/50356"&gt;bug which will trigger two workflow runs&lt;/a&gt;, of which the second will fail.  However future pushes onto the branch should work correctly.&lt;/p&gt;

&lt;p&gt;You'll notice a fair amount of &lt;a href="https://xkcd.com/1171"&gt;RegEx&lt;/a&gt; used and old school tools such as &lt;a href="https://www.gnu.org/software/grep/"&gt;grep&lt;/a&gt; and &lt;a href="https://www.gnu.org/software/gawk/"&gt;awk&lt;/a&gt;&lt;sup id="fnref2"&gt;2&lt;/sup&gt;, which serve to capture the test results and append this data to the &lt;strong&gt;&lt;em&gt;log.json&lt;/em&gt;&lt;/strong&gt; as a new line each time a commit is run.  The appended line looks something like this:&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="nl"&gt;"sha"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"a0d7b73dfb0c5273e6dceb380e7568dfe735d65b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"minus_test_js"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FAIL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"add_test_js"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PASS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PreciousChicken"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of course this capture could have been achieved with something like Python, but seen as the GitHub workflow runs on an Ubuntu virtual machine, GNU tools work just fine.  They also have the advantage that you are implementing all the logic in the workflow itself, which is arguably more transparent and portable.&lt;/p&gt;

&lt;p&gt;NB - If you have sharp eyes you might notice that the filename periods have been changed to underscores in the json e.g. &lt;strong&gt;&lt;em&gt;minus.test.js&lt;/em&gt;&lt;/strong&gt; to &lt;strong&gt;&lt;em&gt;minus_test_js&lt;/em&gt;&lt;/strong&gt;.  This is because vega-lite does not play well with field names that include periods, so to avoid complications these have been swapped out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results and Conclusion
&lt;/h2&gt;

&lt;p&gt;From this point on any further commits will cause the GitHub action to run, the tests to be activated and &lt;strong&gt;&lt;em&gt;log.json&lt;/em&gt;&lt;/strong&gt; to get a new entry, which will be visualised by the vega-lite embedded in the GitHub pages.  This will result in a visualisation that looks similar to this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.preciouschicken.com/blog/images/vegaration-visualisation.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--p3exW5Qd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.preciouschicken.com/blog/images/vegaration-visualisation-thumb.png" alt="Continuous integration visualisation" width="630" height="779"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is worth noting it can take some time for this page to be updated; indeed possibly about ten minutes on first creation according to this &lt;a href="https://stackoverflow.com/questions/24851824/how-long-does-it-take-for-github-page-to-show-changes-after-changing-index-html"&gt;StackOverflow answer&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Clearly this particular visualisation may not have any particular benefit for your project (although seeing which tests are failing more than others and who is the most active participant, might be of use).  However given the flexibility of vega-lite and the option to log just about anything you would want with GitHub actions, there are literally limitless use cases.&lt;/p&gt;

&lt;p&gt;Feedback?  Please leave a message below.  If you like what I've done here please take a moment to star the repo and let me know.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Normally failing tests would halt the progress of GitHub actions - for good reason - a failed test probably means you do not want to deploy.  As this was a visualisation however which was intended to capture both passing and failing tests, although the tests were run their results were captured in a shell environment variable, rather than being outputted to standard output / standard error which would have halted the workflow.  In a production system therefore you would want a seperate workflow which is actually running the tests in their normal manner allowing a return to standard output / standard error. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;Grep was probably redundant and I could have used awk for everything, but it seemed simpler to use both in this instance. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>github</category>
      <category>actions</category>
      <category>vegalite</category>
    </item>
    <item>
      <title>A no-tears guide to adding references in Groff</title>
      <dc:creator>Precious Chicken</dc:creator>
      <pubDate>Tue, 17 Jan 2023 13:10:15 +0000</pubDate>
      <link>https://dev.to/preciouschicken/a-no-tears-guide-to-adding-references-in-groff-542</link>
      <guid>https://dev.to/preciouschicken/a-no-tears-guide-to-adding-references-in-groff-542</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Groff is a decades-old typesetting system present on most Linux distributions which uses an application called refer to add citations.  I found adding citations using this mechanism a challenge, therefore as an aide-memoire this is a minimal worked example of adding references to a Groff document with the refer program.  I'm using GNU Groff version 1.22.4, GNU refer (groff) version 1.22.4 on Manjaro Linux 22.0.0.  I'm using the &lt;a href="https://www.gnu.org/software/groff/manual/html_node/ms.html#ms" rel="noopener noreferrer"&gt;ms macro&lt;/a&gt;, but the basic principles should apply to most macros.  All text examples can be found in the &lt;a href="https://github.com/PreciousChicken/no-tears-reference-groff" rel="noopener noreferrer"&gt;associated github repo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating documents
&lt;/h2&gt;

&lt;p&gt;First create the document we are placing the references into, so create a file named &lt;em&gt;notears.ms&lt;/em&gt; and paste the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.R1 \" Citation commands start
database notears.bib # Path to bibliography file
accumulate # Collates References at end of documentation
move-punctuation # Ensures that citation appears before full-stop.
label "(A.n|Q) ', ' (D.+yD.y%a*D.-y)" # Actual format of citation (e.g. (Author, Date)
bracket-label " (" ) "; " # Bracket style
no-label-in-reference # Does not display full citation (e.g. Author, Date) within References
.R2 \" Citation commands end
.ds FAM H \" Sets font family
.TL
A no-tears guide to adding references in Groff
.AU
PreciousChicken
.AB
The abstract of a no-tears guide to adding references in Groff, see https://preciouschicken.com/blog/posts/no-tears-references-groff/ for more detail.
.AE
.PP
Here is a reference to a story about brain cells playing ping-pong
.[
shepherd22
.]
in my first paragraph.
.PP
And another reference to an AI story
.[
callaway20
.]
in my second paragraph.
.PP
Now we are on the final paragraph and I have my third
.[
nao22tax
.]
and then last reference,
.[
nao22bbc
.]
both from the NAO and both published in 2022.  I've also added a footnote for comparison purposes\**.
.FS \" Footnote start
This is the only footnote.
.FE \" Footnote end.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The section starting &lt;code&gt;.R1&lt;/code&gt; and ending &lt;code&gt;.R2&lt;/code&gt; is where commands are added as to how the citations will displayed.  These commands could also be entered on the command line rather than in the document text - but I prefer the latter as that keeps it all together if you are trying to recreate the document later.  For a full list of the applicable commands see &lt;a href="https://man7.org/linux/man-pages/man1/refer.1.html" rel="noopener noreferrer"&gt;man(1) refer&lt;/a&gt;.  The point where the citations are introduced into the text are marked by the opening &lt;code&gt;.[&lt;/code&gt; and closing &lt;code&gt;.]&lt;/code&gt;, with the keyword of the citation inbetween (e.g. &lt;code&gt;callaway20&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Keen readers may note the comments format for the text between &lt;code&gt;.R1&lt;/code&gt; and &lt;code&gt;.R2&lt;/code&gt; follows a different format (comments begin with &lt;code&gt;#&lt;/code&gt; not &lt;code&gt;\"&lt;/code&gt;) as this section is intended for the preprocessor refer and not groff itself.&lt;/p&gt;

&lt;p&gt;Now we need to create the bibliography file that this document points to, so in the same folder&lt;sup id="fnref1"&gt;1&lt;/sup&gt; create another file named &lt;em&gt;notears.bib&lt;/em&gt; and paste:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%K shepherd22
%A Shepherd, T.
%B London
%D 2022
%T Scientists teach brain cells to play video game Pong
%J The Guardian
%O Available at: https://www.theguardian.com/australia-news/2022/oct/13/scientists-teach-brain-cells-to-play-virtual-pong [Accessed 14 Oct 22]

%K callaway20 
%A Callaway, E.
%B London
%D 2022
%T 'It will change everything': Deepmind's AI makes gigantic leap in solving protein structures.
%J Nature News
%O Available at: https://www.nature.com/articles/d41586-020-03348-4 [Accessed 8 Sep 22]

%K nao22tax
%Q National Audit Office
%C London
%D 2022
%T Managing tax compliance following the pandemic
%O Available at: https://www.nao.org.uk/reports/managing-tax-compliance-following-the-pandemic/ [Accessed 17 Jan 23]

%K nao22bbc
%Q National Audit Office
%C London
%D 2022
%T A digital BBC
%O Available at: https://www.nao.org.uk/reports/a-digital-bbc/ [Accessed 17 Jan 23]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each reference is separated by a blank line, with each field of the reference on a new line.  Each reference line starts with the code for the field, so for instance &lt;code&gt;%C&lt;/code&gt; is city of publication.  The keyword we referred to in &lt;em&gt;notears.ms&lt;/em&gt; is given on the &lt;code&gt;%K&lt;/code&gt; line.  Again for the guide see &lt;a href="https://man7.org/linux/man-pages/man1/refer.1.html" rel="noopener noreferrer"&gt;man(1) refer&lt;/a&gt;.   The mom macro has also &lt;a href="https://schaffter.ca/mom/momdoc/refer.html#fields-quick" rel="noopener noreferrer"&gt;introduced some fields&lt;/a&gt; which were not in the original (for instance &lt;code&gt;%l&lt;/code&gt; as translator).&lt;/p&gt;

&lt;h2&gt;
  
  
  Producing the PDF
&lt;/h2&gt;

&lt;p&gt;We now have enough to produce a document with references.  There are a number of commands&lt;sup id="fnref2"&gt;2&lt;/sup&gt; you can use to produce the final file, but we'll go for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;groff &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; ms &lt;span class="nt"&gt;-T&lt;/span&gt; pdf notears.ms &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; notears.pdf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-R&lt;/code&gt; option here being critical as it informs groff that there are references and to use the refer preprocesser before producing the final document.&lt;/p&gt;

&lt;p&gt;If you now open the resulting &lt;code&gt;notears.pdf&lt;/code&gt; with your pdf viewer of choice you should see:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.preciouschicken.com/blog/images/no-tears.png" rel="noopener noreferrer"&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%2F55s6f75l6nkvl7yvftkm.png" alt="No tears pdf screenshot" width="630" height="889"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Of note is that the National Audit Office citations are disambiguated - i.e. 2022a and 2022b.  This is as a result of the rather cryptic &lt;code&gt;D.+yD.y%a*D.-y&lt;/code&gt; code in the &lt;code&gt;label&lt;/code&gt; field of the document - and is copied straight from &lt;a href="https://man7.org/linux/man-pages/man1/refer.1.html" rel="noopener noreferrer"&gt;man(1) refer&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion &amp;amp; further references
&lt;/h2&gt;

&lt;p&gt;If you've found this useful please leave a comment below or star the github repo.  You might also be interested in a previous post I wrote on &lt;a href="https://www.preciouschicken.com/blog/posts/groff-art-letter-writing/" rel="noopener noreferrer"&gt;producing correspondence letters&lt;/a&gt; in groff or if you are a vim user my vim plugin: &lt;a href="https://preciouschicken.com/software/vim-groff-viewer/" rel="noopener noreferrer"&gt;vim-groff-viewer&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Apart from the &lt;a href="https://man7.org/linux/man-pages/man1/refer.1.html" rel="noopener noreferrer"&gt;man(1) refer&lt;/a&gt; page there is not a huge amount of concise online guidance on including references within groff, however a useful video is Luke Smith's &lt;a href="https://videos.lukesmith.xyz/w/5ANbTYv7cgF69FhpAkVBwi" rel="noopener noreferrer"&gt;Your Brain Using REFER to do your bibliographies automatically in groff/troff&lt;/a&gt;.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;The bibliography can be in another folder, in which case include the full path in the &lt;code&gt;database&lt;/code&gt; command. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;You could also use &lt;code&gt;refer notears.ms | groff -m ms -T pdf &amp;gt; notears.pdf&lt;/code&gt; or &lt;code&gt;grog -T pdf --run notears.ms &amp;gt; notears.pdf&lt;/code&gt; or &lt;code&gt;pdfroff -R -m ms notears.ms --pdf-output=notears.pdf&lt;/code&gt; all of which would produce the same result.  Though interestingly in my testing I noticed that pdfroff used a more up to date version of the PDF format: v1.7 compared to the others using v1.4. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>groff</category>
      <category>linux</category>
    </item>
    <item>
      <title>Sprechen sie GraphSQLite? Or querying data in a SQLite database using GraphQL and Apollo Server</title>
      <dc:creator>Precious Chicken</dc:creator>
      <pubDate>Thu, 02 Jun 2022 11:32:38 +0000</pubDate>
      <link>https://dev.to/preciouschicken/sprechen-sie-graphsqlite-or-querying-data-in-a-sqlite-database-using-graphql-and-apollo-server-46mh</link>
      <guid>https://dev.to/preciouschicken/sprechen-sie-graphsqlite-or-querying-data-in-a-sqlite-database-using-graphql-and-apollo-server-46mh</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;The website &lt;a href="https://www.sqlitetutorial.net/"&gt;sqlitetutorial.net&lt;/a&gt; has some good pointers on using the &lt;a href="https://www.sqlite.org/"&gt;SQLite&lt;/a&gt; database.  This worked example builds on their &lt;a href="https://www.sqlitetutorial.net/sqlite-nodejs/querying-data-sqlite-database-node-js-applications/"&gt;Querying Data in SQLite Database from Node.js Applications&lt;/a&gt; tutorial.  The differences being I'm using the &lt;a href="https://www.npmjs.com/package/better-sqlite3"&gt;better-sqlite3&lt;/a&gt; rather than the &lt;a href="https://www.npmjs.com/package/sqlite3"&gt;sqlite3&lt;/a&gt; npm package&lt;sup id="fnref1"&gt;1&lt;/sup&gt; and I'm using a &lt;a href="https://graphql.org/"&gt;GraphQL&lt;/a&gt; API (powered by &lt;a href="https://www.apollographql.com/docs/apollo-server/"&gt;Apollo Server&lt;/a&gt;) to present the data (as opposed to &lt;code&gt;console.log&lt;/code&gt;).  As per the sqlitetutorial.net site I'm using the sample database &lt;a href="https://github.com/lerocha/chinook-database"&gt;chinook.db&lt;/a&gt;.  This worked example is designed to mimic the tutorial as closely as possible - the queries and responses are almost exactly the same.  It probably isn't a great place to start though if you don't understand the GraphQL fundamentals, in that case you might try &lt;a href="https://www.preciouschicken.com/blog/posts/minimal-graphql-apollo-server/"&gt;Oh-so minimal GraphQL API example with Apollo Server&lt;/a&gt; first.&lt;/p&gt;

&lt;p&gt;All code can be found at the repo &lt;a href="https://github.com/PreciousChicken/sqlite-graphql-apollo-server"&gt;PreciousChicken/sqlite-graphql-apollo-server&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I'm using Manjaro Linux 21.2.6, node v16.13.2, npm v8.3.2, better-sqlite3 v7.5.3 and apollo-server v3.8.1.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initialisation
&lt;/h2&gt;

&lt;p&gt;At the terminal create a directory, and install the relevant 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;mkdir &lt;/span&gt;sqlite-graphql
&lt;span class="nb"&gt;cd &lt;/span&gt;sqlite-graphql
npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
npm &lt;span class="nb"&gt;install &lt;/span&gt;apollo-server better-sqlite3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Download the database
&lt;/h2&gt;

&lt;p&gt;Download and unzip the &lt;a href="https://www.sqlitetutorial.net/wp-content/uploads/2018/03/chinook.zip"&gt;sample Chinook.db database&lt;/a&gt; from sqlitetutorial.net into the folder you just created, ensuring it is named &lt;strong&gt;&lt;em&gt;chinook.db&lt;/em&gt;&lt;/strong&gt;.  If you have &lt;a href="https://curl.se/"&gt;curl&lt;/a&gt; and &lt;a href="https://www.7-zip.org/"&gt;7z&lt;/a&gt; installed you could do this at the command line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://www.sqlitetutorial.net/wp-content/uploads/2018/03/chinook.zip &lt;span class="nt"&gt;--output&lt;/span&gt; chinook.zip
7z x &lt;span class="nt"&gt;-tzip&lt;/span&gt; chinook.zip chinook.db &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm &lt;/span&gt;chinook.zip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create the server
&lt;/h2&gt;

&lt;p&gt;Copy and paste the following into a new file called &lt;strong&gt;&lt;em&gt;index.js&lt;/em&gt;&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ApolloServer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;apollo-server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;typeDefs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resolvers&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./schema&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&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;ApolloServer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;typeDefs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resolvers&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// The `listen` method launches a web server.&lt;/span&gt;
&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Server ready at &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create the schema
&lt;/h2&gt;

&lt;p&gt;Create a file named &lt;strong&gt;&lt;em&gt;schema.js&lt;/em&gt;&lt;/strong&gt; and paste the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;better-sqlite3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chinook.db&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="na"&gt;readonly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fileMustExist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;gql&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;apollo-server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;typeDefs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;gql&lt;/span&gt;&lt;span class="s2"&gt;`
    type Playlist {
        "PlaylistId"
        id: ID
        "Name"
        name: String
    }

    type Customer {
        "Customer ID"
        id: ID
        "First name"
        firstName: String
        "Last name"
        lastName: String
        "Country"
        country: String
        "Email"
        email: String
    }

    type Query {
        playlists: [Playlist]
        playlist(id: ID!): Playlist
        customerLocation(country: String!): [Customer]
    }
`&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resolvers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;playlists&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="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT DISTINCT Name name &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; 
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FROM playlists &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; 
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ORDER BY name&lt;/span&gt;&lt;span class="dl"&gt;'&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="na"&gt;playlist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT PlaylistId id, Name name &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FROM playlists &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; 
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;WHERE PlaylistId  = ?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

        &lt;span class="na"&gt;customerLocation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT FirstName firstName, LastName lastName, Email email &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; 
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FROM customers &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; 
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;WHERE Country = ? &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; 
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ORDER BY FirstName&lt;/span&gt;&lt;span class="dl"&gt;'&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;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;country&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;typeDefs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;typeDefs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolvers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;resolvers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A close examination will notice that the &lt;code&gt;SELECT&lt;/code&gt; in each statement renames the elements - so the column &lt;code&gt;FirstName&lt;/code&gt; in &lt;strong&gt;&lt;em&gt;chinook.db&lt;/em&gt;&lt;/strong&gt; is renamed &lt;code&gt;firstName&lt;/code&gt;.  This is done so that the data returned by the SQL matches the &lt;a href="https://www.apollographql.com/docs/apollo-server/schema/schema/#naming-conventions"&gt;GraphQL naming convention&lt;/a&gt; and which is used for the definitions in the &lt;code&gt;typeDefs&lt;/code&gt;.  Not strictly necessary, but good practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start the server
&lt;/h2&gt;

&lt;p&gt;Having written the code, let's start the server.  At the terminal enter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node index.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Assuming success you should see a message similar to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Server ready at http://localhost:4000/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Apollo Sandbox
&lt;/h2&gt;

&lt;p&gt;Pointing our browser at the URL above we should now be invited to &lt;strong&gt;&lt;em&gt;Query your server&lt;/em&gt;&lt;/strong&gt;.  Below are listed the examples given in the &lt;a href="https://www.sqlitetutorial.net/sqlite-nodejs/querying-data-sqlite-database-node-js-applications/"&gt;sqlitetutorial.net tutorial&lt;/a&gt;, broken down into operation (i.e. query), optional variables and expected response.  Give it a go in the Apollo Sandbox &lt;sup id="fnref2"&gt;2&lt;/sup&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  a. Return all distinct playlists
&lt;/h3&gt;

&lt;p&gt;Corresponds with the &lt;strong&gt;&lt;em&gt;Querying all rows with all() method&lt;/em&gt;&lt;/strong&gt; example.&lt;/p&gt;

&lt;h4&gt;
  
  
  Operation
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Playlists&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="n"&gt;playlists&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="n"&gt;name&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;h4&gt;
  
  
  Response
&lt;/h4&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;"data"&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;"playlists"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"90’s Music"&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;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Audiobooks"&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="err"&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;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;h3&gt;
  
  
  b. Find a playlist given an ID
&lt;/h3&gt;

&lt;p&gt;Corresponds with the &lt;strong&gt;&lt;em&gt;Query the first row in the result set&lt;/em&gt;&lt;/strong&gt; example.&lt;/p&gt;

&lt;h4&gt;
  
  
  Operation
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Playlist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$playlistId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ID&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="n"&gt;playlist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$playlistId&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="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;name&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;h4&gt;
  
  
  Variables
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;playlistId&lt;/span&gt;&lt;span class="err"&gt;":&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;1&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;h4&gt;
  
  
  Response
&lt;/h4&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;"data"&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;"playlist"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Music"&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;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;h3&gt;
  
  
  c. Find all customers in a given country
&lt;/h3&gt;

&lt;p&gt;Corresponds with the &lt;strong&gt;&lt;em&gt;Query rows with each() method&lt;/em&gt;&lt;/strong&gt; example - although we are not using the each() method here, as we are returning the entire set of results with GraphQL and not iterating over the results.&lt;/p&gt;

&lt;h4&gt;
  
  
  Operation
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CustomerLocation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&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="n"&gt;customerLocation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$country&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="n"&gt;firstName&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;lastName&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;email&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;h4&gt;
  
  
  Variables
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="err"&gt;":&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="n"&gt;USA&lt;/span&gt;&lt;span class="err"&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;h4&gt;
  
  
  Response
&lt;/h4&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;"data"&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;"customerLocation"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"firstName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Dan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"lastName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Miller"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dmiller@comcast.com"&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;span class="nl"&gt;"firstName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Frank"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"lastName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Harris"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fharris@google.com"&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="err"&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;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;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Comments, feedback?  Post below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related work
&lt;/h2&gt;

&lt;p&gt;Some other resources I have produced on GraphQL are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.preciouschicken.com/blog/posts/apollo-server-docker-container/"&gt;Tie down scheme for an Apollo GraphQL server in a Node Docker container&lt;/a&gt; - Worked example deploying a GraphQL server in a docker container.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://preciouschicken.com/blog/posts/minimal-graphql-apollo-server/"&gt;Oh-so minimal GraphQL API example with Apollo Server&lt;/a&gt; - A more comprehensive tutorial on GraphQL APIs.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.preciouschicken.com/blog/posts/jest-testing-graphql-api/"&gt;A no jokes guide to testing a GraphQL API with Jest&lt;/a&gt; - Worked example as to testing a GraphQL API.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.preciouschicken.com/blog/posts/vercel-apollo-server-react/"&gt;Stacking Vercel, a GraphQL Apollo Server and React&lt;/a&gt; - Deploying a GraphQL API on Vercel. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;I preferred the documentation of better-sqlite3, plus it claims to be faster. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;Sandbox has replaced Playground, which has been &lt;a href="https://www.apollographql.com/docs/apollo-server/testing/build-run-queries/#graphql-playground"&gt;retired&lt;/a&gt;. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>graphql</category>
      <category>sqlite</category>
      <category>apollo</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Introducing vim-groff-viewer: A vim plugin for displaying Groff files in a document viewer</title>
      <dc:creator>Precious Chicken</dc:creator>
      <pubDate>Wed, 18 May 2022 19:58:44 +0000</pubDate>
      <link>https://dev.to/preciouschicken/introducing-vim-groff-viewer-a-vim-plugin-for-displaying-groff-files-in-a-document-viewer-2ff6</link>
      <guid>https://dev.to/preciouschicken/introducing-vim-groff-viewer-a-vim-plugin-for-displaying-groff-files-in-a-document-viewer-2ff6</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;I like (Neo)vim, I like Groff. So much so that I’ve &lt;a href="https://www.preciouschicken.com/blog/posts/groff-art-letter-writing/"&gt;blogged about letter writing in Groff&lt;/a&gt; before. What I don’t like is writing markup in Neovim, exiting Neovim, compiling Groff, opening a document viewer and then going back into Neovim to edit again. And repeat.&lt;/p&gt;

&lt;p&gt;To remedy this my first vim plugin - &lt;strong&gt;vim-groff-viewer&lt;/strong&gt;. It is probably easier to demonstrate, than explain, so…&lt;/p&gt;

&lt;h2&gt;
  
  
  Video demonstration
&lt;/h2&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/-NOvAKriByM"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Download, usage, configuration, etc
&lt;/h2&gt;

&lt;p&gt;Interested? Find more at &lt;a href="https://preciouschicken.com/software/vim-groff-viewer/"&gt;preciouschicken.com/software/vim-groff-viewer/&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>groff</category>
      <category>neovim</category>
      <category>vim</category>
      <category>plugin</category>
    </item>
    <item>
      <title>Look Ma, no Neovim plugin manager!</title>
      <dc:creator>Precious Chicken</dc:creator>
      <pubDate>Sat, 14 May 2022 06:47:32 +0000</pubDate>
      <link>https://dev.to/preciouschicken/look-ma-no-neovim-plugin-manager-2ckk</link>
      <guid>https://dev.to/preciouschicken/look-ma-no-neovim-plugin-manager-2ckk</guid>
      <description>&lt;h2&gt;
  
  
  Handsfree
&lt;/h2&gt;

&lt;p&gt;To manually install a &lt;a href="https://neovim.io/"&gt;Neovim&lt;/a&gt; plugin, for example &lt;a href="https://github.com/tpope/vim-commentary"&gt;Tim Pope's Commentary&lt;/a&gt;, without using a plug-in manager then at the terminal :&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; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/.local/share/nvim/site/pack/tpope/start/commentary
git clone https://tpope.io/vim/commentary.git ~/.local/share/nvim/site/pack/tpope/start/commentary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The install path always follows the pattern of &lt;em&gt;~/.local/share/nvim/site/pack/foo/start/bar&lt;/em&gt;, where the variables &lt;em&gt;foo&lt;/em&gt; and &lt;em&gt;bar&lt;/em&gt; can be whatever makes sense to you.  Commonly &lt;em&gt;foo&lt;/em&gt; is the name of the author (e.g. tpope) and &lt;em&gt;bar&lt;/em&gt; is the name of the plugin (e.g. commentary).&lt;/p&gt;

&lt;h2&gt;
  
  
  Automation
&lt;/h2&gt;

&lt;p&gt;Which is great, but from time to time your plugins will get updated and you will need to pull fresh versions.  This can easily be achieved on Linux systems with a small script.  Create a file named &lt;em&gt;~/.local/bin/vimpluginupdate&lt;/em&gt; and paste the following:&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;#!/bin/sh&lt;/span&gt;
&lt;span class="nv"&gt;NVMPTH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.local/share/nvim/site/pack"&lt;/span&gt;
git &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="nv"&gt;$NVMPTH&lt;/span&gt;/tpope/start/commentary pull &lt;span class="o"&gt;||&lt;/span&gt; git clone &lt;span class="nt"&gt;--depth&lt;/span&gt; 1 https://tpope.io/vim/commentary.git &lt;span class="nv"&gt;$NVMPTH&lt;/span&gt;/tpope/start/commentary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rather long git line pulls the repository if it already exists, and if it does not exist (i.e. you haven't done the first step) then it clones a fresh one (using the &lt;em&gt;depth&lt;/em&gt; option to just get the latest version and not previous changes).&lt;/p&gt;

&lt;p&gt;Now make the file 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;u+x ~/.local/bin/vimpluginupdate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And whenever you want to update your plugins run&lt;sup id="fnref1"&gt;1&lt;/sup&gt;:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vimpluginupdate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you install more updates simply add new lines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Helptags
&lt;/h2&gt;

&lt;p&gt;To load any help documentation associated with the plugin then run &lt;code&gt;:helptags ALL&lt;/code&gt; within Neovim when the plugin is active.  So for instance the vim-commentary plugin will be active when Neovim has open any file recognised as a programming language (i.e. any file with an extension &lt;em&gt;.js&lt;/em&gt;, &lt;em&gt;.py&lt;/em&gt;, etc).  In vim-commentary's case the help docs can then be accessed with &lt;code&gt;:help commentary&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  But I use vim?
&lt;/h2&gt;

&lt;p&gt;All I've really done here is amended the &lt;a href="https://github.com/tpope/vim-commentary#installation"&gt;vim-commentary install section&lt;/a&gt;, so check that out for standard vim (NB - the main difference is the directory used).&lt;/p&gt;

&lt;h2&gt;
  
  
  I demand an authoritative source
&lt;/h2&gt;

&lt;p&gt;See &lt;a href="https://neovim.io/doc/user/repeat.html#packages"&gt;:help packages&lt;/a&gt; and &lt;a href="https://neovim.io/doc/user/usr_05.html#05.4"&gt;:help add-package&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Version control
&lt;/h2&gt;

&lt;p&gt;This was tested corrected on Ubuntu Linux 22.04 LTS and Neovim v0.6.1.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Interestingly the &lt;em&gt;~/.local/bin/&lt;/em&gt; directory is not added to your &lt;em&gt;PATH&lt;/em&gt; &lt;a href="https://askubuntu.com/a/1144235"&gt;on a fresh install&lt;/a&gt;, so if the command does not work at first, log out then in again. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>neovim</category>
      <category>bash</category>
      <category>vim</category>
    </item>
    <item>
      <title>Étoile du jour: Deploying NASA's Astronomy Picture of the Day on Azure (Microsoft Azure Trial Hackathon)</title>
      <dc:creator>Precious Chicken</dc:creator>
      <pubDate>Sun, 20 Feb 2022 20:37:04 +0000</pubDate>
      <link>https://dev.to/preciouschicken/etoile-du-jour-deploying-nasas-astronomy-picture-of-the-day-as-an-azure-web-app-4gg</link>
      <guid>https://dev.to/preciouschicken/etoile-du-jour-deploying-nasas-astronomy-picture-of-the-day-as-an-azure-web-app-4gg</guid>
      <description>&lt;h3&gt;
  
  
  Overview of My Submission
&lt;/h3&gt;

&lt;p&gt;A React Single Page Application deployed as an Azure Web App allowing users to select dates from NASA's Astronomy Photo of the Day archive (accessed via a NASA API).&lt;/p&gt;

&lt;p&gt;The app is live at: &lt;a href="https://nasa-apod-picker.azurewebsites.net/"&gt;nasa-apod-picker.azurewebsites.net&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Submission Category
&lt;/h3&gt;

&lt;p&gt;Wacky Wildcards&lt;/p&gt;

&lt;h3&gt;
  
  
  Link to Code on GitHub
&lt;/h3&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--566lAguM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/PreciousChicken"&gt;
        PreciousChicken
      &lt;/a&gt; / &lt;a href="https://github.com/PreciousChicken/azure-react-apod"&gt;
        azure-react-apod
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A demonstration of NASA's Astronomy Picture of the Day (APOD) using the React framework, hosted as an Azure web app.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
NASA's APOD Picker&lt;/h1&gt;
&lt;p&gt;A demonstration of NASA's Astronomy Picture of the Day (APOD) using the React framework, hosted as an Azure web app; designed to accompany the &lt;a href="https://www.preciouschicken.com/blog/posts/azure-react-apod/" rel="nofollow"&gt;Étoile du jour: Deploying a React Web App on the Microsoft Azure Cloud&lt;/a&gt; tutorial.&lt;/p&gt;
&lt;p&gt;The deployed final version can be seen at: &lt;a href="https://nasa-apod-picker.azurewebsites.net/" rel="nofollow"&gt;nasa-apod-picker.azurewebsites.net/&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/PreciousChicken/azure-react-apod"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


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

&lt;h4&gt;
  
  
  Screenshot of app:
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://www.preciouschicken.com/blog/images/azure-react-apod_final.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--guTJzYIY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.preciouschicken.com/blog/images/azure-react-apod_final-thumb.png" alt="NASA Astronomy Picture of the Day Picker screenshot" width="630" height="385"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Accompanying worked example tutorials:
&lt;/h4&gt;

&lt;h5&gt;
  
  
  Video -
&lt;/h5&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/b41G09Xft88"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h5&gt;
  
  
  Blog -
&lt;/h5&gt;

&lt;p&gt;&lt;a href="https://www.preciouschicken.com/blog/posts/azure-react-apod/"&gt;Étoile du jour: Deploying a React Web App on the Microsoft Azure Cloud&lt;/a&gt;&lt;/p&gt;

</description>
      <category>azuretrialhack</category>
      <category>react</category>
      <category>node</category>
    </item>
    <item>
      <title>Linking to a heading in the same page in Hugo</title>
      <dc:creator>Precious Chicken</dc:creator>
      <pubDate>Sun, 20 Feb 2022 14:58:24 +0000</pubDate>
      <link>https://dev.to/preciouschicken/linking-to-the-same-page-in-hugo-llb</link>
      <guid>https://dev.to/preciouschicken/linking-to-the-same-page-in-hugo-llb</guid>
      <description>&lt;p&gt;Linking to another page in markdown in &lt;a href="https://gohugo.io/"&gt;Hugo&lt;/a&gt; is easy: i.e. &lt;code&gt;[Auntie Beeb](https://www.bbc.co.uk/)&lt;/code&gt; renders as &lt;a href="https://www.bbc.co.uk/"&gt;Auntie Beeb&lt;/a&gt; - but when you want a link to a heading in the same page I can never remember, so this is a quick aide-memoire:&lt;/p&gt;

&lt;h2&gt;
  
  
  HTML
&lt;/h2&gt;

&lt;p&gt;In HTML a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#linking_to_an_element_on_the_same_page"&gt;relative link to an element in the same page&lt;/a&gt; looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- &amp;lt;a&amp;gt; element links to the section below --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#Section_further_down"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Jump to the heading below
&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Heading to link to --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"Section_further_down"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Section further down&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Hugo markdown
&lt;/h2&gt;

&lt;p&gt;In Hugo to achieve the same effect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- &amp;lt;a&amp;gt;&lt;/span&gt; element links to the section below --&amp;gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Jump to the heading below&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;#section-further-down&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Heading to link to --&amp;gt;&lt;/span&gt;
&lt;span class="gu"&gt;## Section further down&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>hugo</category>
      <category>html</category>
      <category>markdown</category>
    </item>
    <item>
      <title>Étoile du jour: Deploying a React Web App on the Microsoft Azure Cloud</title>
      <dc:creator>Precious Chicken</dc:creator>
      <pubDate>Fri, 21 Jan 2022 22:16:06 +0000</pubDate>
      <link>https://dev.to/preciouschicken/etoile-du-jour-deploying-a-react-web-app-on-the-microsoft-azure-cloud-4gfd</link>
      <guid>https://dev.to/preciouschicken/etoile-du-jour-deploying-a-react-web-app-on-the-microsoft-azure-cloud-4gfd</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hosting a React framework Single Page Application as a Web App on the Microsoft Azure can be an extremely frustrating process, for a number of reasons.  To start with despite React being (rightly or wrongly) the &lt;a href="https://www.statista.com/statistics/1124699/worldwide-developer-survey-most-used-frameworks-web/"&gt;most popular web framework&lt;/a&gt;, there are no specific instructions within the Microsoft documentation for how one might deploy a React Single Page Application (SPA) as an Azure Web App.  Far worse however is that the choice of operating system (Windows or Linux) when creating a React web app on Azure is key - in short it is &lt;a href="https://github.com/MicrosoftDocs/azure-docs/issues/32572#issuecomment-637832128"&gt;very difficult to use create-react-app to deploy to a base Linux O/S&lt;/a&gt; - yet this is both unintuitive and seemingly undocumented (see the What's up with Linux? section).&lt;/p&gt;

&lt;p&gt;And continuous integration / continuous development via Github workflow is painful on Azure too; I couldn't find a single explanation which worked from start to finish without inexplicably failing half way through.  As an aside the access the Azure App Service, which I'm not using in this demo, wants of your Github account is onerous too (you can't restrict access to private repos - which, er, are meant to be private).&lt;/p&gt;

&lt;p&gt;What follows therefore is a fully worked example of how to deploy a React Single Page Application to an Azure web app.  GitHub will be used for continuous integration / continuous development.  It assumes you have an Azure account, a GitHub account and node / npm installed.  Some common command line tools are also used: sed and curl.  Development is being done on Manjaro Linux 21.2.1, Node 16.13.2, npm 8.3.2.  To give the demo more flavour, I am going to use the &lt;a href="https://github.com/nasa/apod-api"&gt;NASA Astronomy Picture of the Day API&lt;/a&gt; to make a fully featured app - you don't have to do this though, Section 3 can be omitted if you just want to deploy the create-react-app template to Azure.&lt;/p&gt;

&lt;p&gt;The steps below are also demonstrated in an accompanying video: &lt;iframe width="710" height="399" src="https://www.youtube.com/embed/b41G09Xft88"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;All code is available on the &lt;a href="https://github.com/PreciousChicken/azure-react-apod"&gt;PreciousChicken/azure-react-apod&lt;/a&gt; GitHub repository.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.  Set up an Azure Web App
&lt;/h3&gt;

&lt;p&gt;First we need to set up a Web App on Azure.  From the Azure portal select &lt;em&gt;Create a Resource&lt;/em&gt; and then &lt;em&gt;Web App&lt;/em&gt;.  Go through the various options until you are faced with a confirmation screen that looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MAWdnfVi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.preciouschicken.com/blog/images/azure-react-apod-create_web_app.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MAWdnfVi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.preciouschicken.com/blog/images/azure-react-apod-create_web_app.png" alt="Azure create Web App" width="507" height="775"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The key point here is that the &lt;em&gt;Operating System&lt;/em&gt; is given as &lt;em&gt;Windows&lt;/em&gt;, if &lt;em&gt;Linux&lt;/em&gt; is selected it will not work (see What's up with Linux?).  You might want to check that you are on the free plan too (&lt;em&gt;F1&lt;/em&gt;) as opposed to the paid for (&lt;em&gt;B1&lt;/em&gt;) which is the default.  We've named the app &lt;em&gt;nasa-apod-picker&lt;/em&gt; but you will need to give it a different name to prevent conflict.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.  Use create-react-app to generate the React SPA
&lt;/h3&gt;

&lt;p&gt;On our local machine from the terminal enter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-react-app azure-react-apod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A React SPA will now be created using &lt;a href="https://create-react-app.dev/"&gt;create-react-app&lt;/a&gt; tool.&lt;/p&gt;

&lt;p&gt;Once installed change directory into the App:&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;cd &lt;/span&gt;azure-react-apod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3.  Make some changes to the React App
&lt;/h3&gt;

&lt;p&gt;If you are just interested in uploading any old React Single Page Application to Azure then you can safely skip this section and leave the default create-react-app settings in place.&lt;/p&gt;

&lt;p&gt;However that does seems a bit dull.  The (somewhat detailed) instructions below will let us create a rather lovely app where we can pick NASA's Astronomy Photo of the Day by date.&lt;/p&gt;

&lt;h4&gt;
  
  
  3a.  Change app title and description
&lt;/h4&gt;

&lt;p&gt;Change the App title and description to something more meaningful (we could do this by editing the &lt;em&gt;public/index.html&lt;/em&gt; file, but using &lt;a href="https://www.gnu.org/software/sed/"&gt;sed&lt;/a&gt; from the terminal is quicker:&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;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/React App/NASA APOD Picker/g'&lt;/span&gt; public/index.html
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/Web site created using create-react-app/Date picker for NASAs Astronomy Picture of the Day/g'&lt;/span&gt; public/index.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3b.  Replace logos
&lt;/h4&gt;

&lt;p&gt;The favicon and logos are currently the default create-react-app one; so we'll replace that by using &lt;a href="https://curl.se/"&gt;curl&lt;/a&gt; to overwrite them with suitably space-themed ones&lt;sup id="fnref1"&gt;1&lt;/sup&gt; (feel free to use whatever files you want, or simply leave the c-r-a one in place):&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://www.preciouschicken.com/blog/images/azure-react-apod_favicon.ico &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; public/favicon.ico
curl https://www.preciouschicken.com/blog/images/azure-react-apod_logo192.png &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; public/logo192.png
curl https://www.preciouschicken.com/blog/images/azure-react-apod_logo512.png &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; public/logo512.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3c.  Replace App.js file
&lt;/h4&gt;

&lt;p&gt;The meat of the change, erase the contents of the &lt;em&gt;src/App.js&lt;/em&gt; file and replace with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./App.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;isomorphic-fetch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;TextField&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;@mui/material/TextField&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;AdapterDateFns&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;@mui/lab/AdapterDateFns&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;LocalizationProvider&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;@mui/lab/LocalizationProvider&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;enLocale&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;date-fns/locale/en-GB&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;DatePicker&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;@mui/lab/DatePicker&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isWithinInterval&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;date-fns&lt;/span&gt;&lt;span class="dl"&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;App&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;apod&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setApod&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;userDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setUserdate&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsLoading&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;hasError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setHasError&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;errorState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setErrorstate&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Earliest photo in APOD&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apodEarliest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1995&lt;/span&gt;&lt;span class="p"&gt;,&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;16&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
  &lt;span class="c1"&gt;// Latest photo in APOD, ie today&lt;/span&gt;
  &lt;span class="c1"&gt;// Hours set to midnight to ensure user selection of date+time always before this date&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apodLatest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;setHours&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;59&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;59&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 

  &lt;span class="c1"&gt;//NASA API URL&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.nasa.gov/planetary/apod?api_key=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REACT_APP_NASA_API_KEY&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;date=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Fetches photo from NASA API&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetchPost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uDate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;setIsLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;setHasError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;try&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;uDate&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;&amp;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="nx"&gt;setErrorstate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;hasError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;setApod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;setHasError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;setIsLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;// Sets userDate as today if null&lt;/span&gt;
  &lt;span class="c1"&gt;// e.g. loads today's photo on first render&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;userDate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;setUserdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="nx"&gt;fetchPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;yyyy-MM-dd&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="c1"&gt;// Adjusts HTML for image vs video&lt;/span&gt;
  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;MediaType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;media&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;mediaLink&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// Determines whether APOD picture is image or video&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;media&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;mediaLink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;apod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;apod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;mediaLink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;iframe&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;apod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;apod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;allowFullScreen&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;mediaLink&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;strong&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;apod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;strong&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;apod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;explanation&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Copyright: &lt;span class="si"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;apod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;copyright&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;apod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;copyright&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NASA (Public Domain)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Component that serves photo, with error handling&lt;/span&gt;
  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Picture&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Return if server has error&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;hasError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"userMsg"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Error connecting to server. 
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;br&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
          Server response: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;errorState&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// Return when waiting for API&lt;/span&gt;
    &lt;span class="k"&gt;else&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;isLoading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"userMsg"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Loading...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// Return once user has selected date and no longer loading and no error&lt;/span&gt;
    &lt;span class="k"&gt;else&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;userDate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;MediaType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;media_type&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// Default return if date not chosen or no error&lt;/span&gt;
    &lt;span class="c1"&gt;// This should never return: userDate is set as today if null on load&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"userMsg"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Select a date between 16-Jun-1995 and today&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;NASA Astronomy Picture of the Day Picker&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"datepicker"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LocalizationProvider&lt;/span&gt; &lt;span class="na"&gt;dateAdapter&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;AdapterDateFns&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;enLocale&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DatePicker&lt;/span&gt; 
            &lt;span class="na"&gt;minDate&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;apodEarliest&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
            &lt;span class="na"&gt;maxDate&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;apodLatest&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;userDate&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
            &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newDate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isWithinInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nx"&gt;newDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;apodEarliest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;apodLatest&lt;/span&gt; &lt;span class="p"&gt;}))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;setUserdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newDate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="nx"&gt;fetchPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newDate&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;yyyy-MM-dd&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
              &lt;span class="p"&gt;};&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
            &lt;span class="na"&gt;renderInput&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&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;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TextField&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;LocalizationProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Picture&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"footer"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"https://apod.nasa.gov/apod/lib/about_apod.html"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;About NASA's Astronomy Picture of the Day&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"footer"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;This page is part of the web development tutorial: &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;br&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"https://www.preciouschicken.com/blog/posts/azure-react-apod/"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Étoile du jour: Deploying a React Web App on the Microsoft Azure Cloud&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3d.  Replace App.css file
&lt;/h4&gt;

&lt;p&gt;Erase the contents of the &lt;em&gt;src/App.css&lt;/em&gt; file and replace with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;black&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="nc"&gt;.datepicker&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;75ch&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin-right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;iframe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;500px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="nc"&gt;.userMsg&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;x-large&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="nc"&gt;.footer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;small&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;h4&gt;
  
  
  3e.  Replace App.test.js
&lt;/h4&gt;

&lt;p&gt;When we continuously deploy using GitHub it automatically runs through any test files prior to deploying.  Create-react-app automatically creates ones of these, &lt;em&gt;src/App.test.js&lt;/em&gt;, for you and this will fail if you make any changes to the &lt;em&gt;src/App.js&lt;/em&gt; file.  You can either therefore delete &lt;em&gt;src/App.test.js&lt;/em&gt; or amend it to include updated tests; as tests are good I've done the latter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;screen&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;@testing-library/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./App&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;renders H1 correctly&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;App&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;nasaHeader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;heading&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/nasa astronomy picture of the day picker/i&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nasaHeader&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;


&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;renders link to APOD about&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;App&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;nasaAbout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;link&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/about nasa's astronomy picture of the day/i&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nasaAbout&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBeInTheDocument&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;h4&gt;
  
  
  3f.  Install additional node dependencies
&lt;/h4&gt;

&lt;p&gt;At the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i date-fns isomorphic-fetch @mui/material @emotion/react @emotion/styled @mui/lab
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4.  Add a workflow to Github
&lt;/h3&gt;

&lt;p&gt;We now need to tell GitHub how to deploy the web app to Azure by adding a workflow.&lt;/p&gt;

&lt;p&gt;Therefore create a new file, named &lt;em&gt;azure-webapps-node.yml&lt;/em&gt;,  located in a new GitHub workflow directory on your machine:&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; &lt;span class="nt"&gt;-p&lt;/span&gt;  ./.github/workflows/ &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;touch&lt;/span&gt; ./.github/workflows/azure-webapps-node.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then copy and paste the following into this &lt;em&gt;.github/workflows/azure-webapps-node.yml&lt;/em&gt; file (changing the name of the &lt;em&gt;env&lt;/em&gt; variables to suit your Azure configuration):&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;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# change to your application's name in Azure&lt;/span&gt;
  &lt;span class="na"&gt;AZURE_WEBAPP_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nasa-apod-picker&lt;/span&gt;    
  &lt;span class="c1"&gt;# change both instances of azure-react-apod to the name of your repo&lt;/span&gt;
  &lt;span class="na"&gt;AZURE_WEBAPP_PACKAGE_PATH&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/home/runner/work/azure-react-apod/azure-react-apod'&lt;/span&gt;   
  &lt;span class="c1"&gt;# set this to the node version to use if not 16&lt;/span&gt;
  &lt;span class="na"&gt;NODE_VERSION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;16.x'&lt;/span&gt;                
  &lt;span class="c1"&gt;# NASA API key recorded as GitHub secret, delete if you haven't followed APOD section&lt;/span&gt;
  &lt;span class="na"&gt;REACT_APP_NASA_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.REACT_APP_NASA_API_KEY }}&lt;/span&gt; 

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&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;Set up Node.js&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v2&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.NODE_VERSION }}&lt;/span&gt;
        &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;npm'&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm install, build, and test&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;npm install&lt;/span&gt;
        &lt;span class="s"&gt;npm run build --if-present&lt;/span&gt;
        &lt;span class="s"&gt;npm run test --if-present        &lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload artifact for deployment job&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v2&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node-app&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./build&lt;/span&gt;

  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
    &lt;span class="na"&gt;environment&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Development'&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.deploy-to-webapp.outputs.webapp-url }}&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;Download artifact from build job&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/download-artifact@v2&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node-app&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Deploy&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Azure&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;WebApp'&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;deploy-to-webapp&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;azure/webapps-deploy@v2&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.AZURE_WEBAPP_NAME }}&lt;/span&gt;
        &lt;span class="na"&gt;publish-profile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}&lt;/span&gt;
        &lt;span class="na"&gt;package&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.AZURE_WEBAPP_PACKAGE_PATH }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The majority of the above yaml code came from websitebeaver.com's &lt;a href="https://websitebeaver.com/deploy-create-react-app-to-azure-app-services"&gt;Deploy Create React App to Azure App Services&lt;/a&gt; tutorial; however a number of changes were made.  The addition of the &lt;em&gt;process.json&lt;/em&gt; code step was removed as it was redundant &lt;sup id="fnref2"&gt;2&lt;/sup&gt;.  Also the &lt;em&gt;AZURE_WEBAPP_PACKAGE_PATH&lt;/em&gt; was changed from &lt;code&gt;build&lt;/code&gt; to &lt;code&gt;/home/runner/work/azure-react-apod/azure-react-apod&lt;/code&gt;, the latter being the location that the build apparently stores the zipped Web App in ready for upload to Azure &lt;sup id="fnref3"&gt;3&lt;/sup&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.  Create an empty Github repo
&lt;/h3&gt;

&lt;p&gt;We are going to enable continuous deployment (CD) on this project via Github.  Therefore point your browser at GitHub and &lt;a href="https://docs.github.com/en/get-started/quickstart/create-a-repo"&gt;follow the instructions&lt;/a&gt; to create a new repo entitled &lt;em&gt;azure-react-apod&lt;/em&gt; (though don't add a README, .gitignore or licence).  We don't actually need to push the project at this stage however.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.  Download publish profile from Azure
&lt;/h3&gt;

&lt;p&gt;Now navigate to your Web App within the Azure portal and select the &lt;em&gt;Get publish profile&lt;/em&gt; option, as shown below, to download a file named &lt;em&gt;your_app_name.PublishSettings&lt;/em&gt; (e.g. &lt;em&gt;nasa-apod-picker.PublishSettings&lt;/em&gt;) to your local machine:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.preciouschicken.com/blog/images/azure-react-apod_get_publish_profile.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lARUzUa_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.preciouschicken.com/blog/images/azure-react-apod_get_publish_profile-thumb.png" alt="Azure portal get publish profile" width="630" height="54"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  7.  Add secrets to GitHub repo
&lt;/h3&gt;

&lt;p&gt;Following the GitHub guide &lt;a href="https://docs.github.com/en/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-a-repository"&gt;Creating encrypted secrets for a repository&lt;/a&gt; create a new secret with the name &lt;em&gt;AZURE_WEBAPP_PUBLISH_PROFILE&lt;/em&gt; and copy and paste the contents of the publish profile you downloaded at the last step.  To make this easier on Linux you can copy a file into the clipboard from the terminal (assuming you have &lt;a href="http://www.vergenet.net/~conrad/software/xsel/"&gt;xsel&lt;/a&gt; installed) like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;xsel &lt;span class="nt"&gt;-b&lt;/span&gt; &amp;lt; ~/Downloads/nasa-apod-picker.PublishSettings
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This secret will allow GitHub to access Azure portal in order to deploy your web app on your behalf.&lt;/p&gt;

&lt;p&gt;If you have been following the Astronomy Picture of the Day instructions in Section 3, then you will also need to &lt;a href="https://api.nasa.gov/"&gt;generate a NASA API key&lt;/a&gt; too.  Go through the steps again to add this key to GitHub as a repository secret, but this time with the name &lt;em&gt;REACT_APP_NASA_API_KEY&lt;/em&gt; &lt;sup id="fnref4"&gt;4&lt;/sup&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  8.  Git push
&lt;/h3&gt;

&lt;p&gt;All that is left to do is the initial push of the project to our repo and GitHub and Azure will take care of the rest.  Digital Ocean features a good tutorial on &lt;a href="https://www.digitalocean.com/community/tutorials/how-to-push-an-existing-project-to-github"&gt;pushing an existing project to GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As a reminder the basic terminal commands, changing the github PreciousChicken url to your own, are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Initial commit"&lt;/span&gt;
git remote add origin git@github.com:PreciousChicken/azure-react-apod.git
git branch &lt;span class="nt"&gt;-M&lt;/span&gt; main
git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The final product
&lt;/h2&gt;

&lt;p&gt;If all has gone to plan your app should be deployed, depending on your Azure set-up configuration, at an address following the format &lt;em&gt;your_app_name.azurewebsites.net&lt;/em&gt;, e.g. &lt;a href="https://nasa-apod-picker.azurewebsites.net/"&gt;nasa-apod-picker.azurewebsites.net&lt;/a&gt;; as demonstrated:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.preciouschicken.com/blog/images/azure-react-apod_final.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--guTJzYIY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.preciouschicken.com/blog/images/azure-react-apod_final-thumb.png" alt="NASA Astronomy Picture of the Day Picker screenshot" width="630" height="385"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What's up with Linux?
&lt;/h2&gt;

&lt;p&gt;My default is Linux in any form of virtual machine environment.  As you will note however this example uses a Windows base O/S; Linux and React did not play well within Azure.&lt;/p&gt;

&lt;p&gt;The nub of the problem is that once you've created a React app the actual part that gets shown to the public is produced by running &lt;code&gt;npm run build&lt;/code&gt; and then serving the resulting web pages found in the &lt;em&gt;build&lt;/em&gt; folder to the web.  Everything outside the build folder is for development and not public view.  Often this detail is taken care of behind the scenes, so for instance the &lt;a href="https://vercel.com/"&gt;Vercel&lt;/a&gt; platform knows what a React SPA is and does the donkey work for you (see &lt;a href="https://www.preciouschicken.com/blog/posts/vercel-apollo-server-react/"&gt;Stacking Vercel, a GraphQL Apollo Server and React&lt;/a&gt; for more).  To replicate this in Azure you manually have to upload the contents of the &lt;em&gt;build&lt;/em&gt; folder to &lt;a href="https://github.com/projectkudu/kudu/wiki/File-structure-on-azure"&gt;Azure's &lt;em&gt;/site/wwwroot&lt;/em&gt; directory&lt;/a&gt;.  However this only works if you have chosen Windows as your O/S when creating the Web App, if you have chosen Linux it simply does not work.  No explanation given, and a couple of hours consigned to trying to figure out where you have gone wrong.&lt;/p&gt;

&lt;p&gt;There are answers by &lt;a href="https://stackoverflow.com/a/61386411/6333825"&gt;Lutti Coelho&lt;/a&gt; and &lt;a href="https://stackoverflow.com/a/60594983/6333825"&gt;bach vo&lt;/a&gt; on StackOverflow which fix this problem by amending the configurations in your Azure dashboard.  I can't vouch for this solution, but clearly it adds one more point of failure if you haven't defaulted to Windows in your original set up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-gb/learn/modules/host-a-web-app-with-azure-app-service/?WT.mc_id=azureportalcard_Service_App%20Services_-inproduct-azureportal"&gt;Host a web application with Azure App Service&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://websitebeaver.com/deploy-create-react-app-to-azure-app-services"&gt;Deploy Create React App to Azure App Services&lt;/a&gt; -  A good starter for ten, but following this only got me ninety percent of the way there.&lt;/li&gt;
&lt;/ul&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Photo of moon by &lt;a href="https://commons.wikimedia.org/wiki/File:Moon.jpg"&gt;OldakQuill, Public domain, via Wikimedia Commons&lt;/a&gt;. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;At first glance it looks as if this might enable the web app to run with Linux O/S on Azure (see the What's up with Linux? section), but it didn't. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;I would like to say I figured this out though deductive reasoning; but in reality I watched the build fail many times, read the error messages and eventually worked out what directory the zip file was in... ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;If you are going to run this locally by using &lt;code&gt;npm start&lt;/code&gt; as well as via GitHub then you will also need to create a file named &lt;em&gt;.env.local&lt;/em&gt; in the root folder of your directory with the content &lt;code&gt;REACT_APP_NASA_API_KEY=your_key_here&lt;/code&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>react</category>
      <category>github</category>
      <category>azure</category>
      <category>node</category>
    </item>
    <item>
      <title>A concrete Python 'Hello World' with the Neovim API</title>
      <dc:creator>Precious Chicken</dc:creator>
      <pubDate>Mon, 27 Dec 2021 21:20:42 +0000</pubDate>
      <link>https://dev.to/preciouschicken/a-concrete-python-hello-world-with-the-neovim-api-33k0</link>
      <guid>https://dev.to/preciouschicken/a-concrete-python-hello-world-with-the-neovim-api-33k0</guid>
      <description>&lt;p&gt;Neovim's API is typically called through a Remote Procedure Call (RPC) using the &lt;a href="https://github.com/msgpack-rpc/msgpack-rpc/blob/master/spec.md"&gt;MessagePack-RPC&lt;/a&gt; specification.  The &lt;a href="https://neovim.io/doc/user/api.html#rpc-connecting"&gt;neovim documentation&lt;/a&gt; provides a 'Hello World' example of how to interface with the Neovim API; this post is a worked example of how to implement this - more of a note to self than anything else.  I'm running Neovim v 0.6.0 on Manjaro 21.2.0 Qonos linux.&lt;/p&gt;

&lt;p&gt;First we need to determine the &lt;a href="https://neovim.io/doc/user/eval.html#v:servername"&gt;servername&lt;/a&gt; that Neovim has set on startup.  Fire up an instance of nvim from the terminal with the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nvim &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"echo v:servername"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-c&lt;/code&gt; &lt;a href="https://neovim.io/doc/user/starting.html#-c"&gt;command option&lt;/a&gt; used above causes neovim to run a command on opening, in this case to print out the servername of the instance.  Resultantly we should see the servername of the instance in the statusline; it should look a little like: &lt;code&gt;/tmp/nvimt7lIuM/0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We can now use this address to command the neovim instance remotely.  Create a file named &lt;code&gt;nvim-hw-api.py&lt;/code&gt; and paste the following - altering the path to match the servername we found above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pynvim&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;attach&lt;/span&gt;
&lt;span class="n"&gt;nvim&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;attach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'socket'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'/tmp/nvimt7lIuM/0'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;nvim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'echo "hello world!"'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the terminal run (assuming &lt;a href="https://wiki.python.org/moin/BeginnersGuide/Download"&gt;Python3 is installed&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 nvim-hw-api.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And if all works as forecast we should see &lt;code&gt;hello world!&lt;/code&gt; appear in the statusline of the neovim instance!&lt;/p&gt;

</description>
      <category>neovim</category>
      <category>python</category>
    </item>
    <item>
      <title>Groff and the art of letter writing</title>
      <dc:creator>Precious Chicken</dc:creator>
      <pubDate>Sun, 11 Jul 2021 15:55:45 +0000</pubDate>
      <link>https://dev.to/preciouschicken/groff-and-the-art-of-letter-writing-2l1e</link>
      <guid>https://dev.to/preciouschicken/groff-and-the-art-of-letter-writing-2l1e</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Although I've blogged previously about using &lt;a href="https://www.preciouschicken.com/blog/posts/neovim-latex-zathura-in-perfect-harmony/"&gt;LaTeX for academic writing&lt;/a&gt; I wanted to try &lt;a href="https://www.gnu.org/software/groff/"&gt;groff&lt;/a&gt; out for the less complicated task of letter writing - groff being similar to LaTeX but &lt;a href="https://unix.stackexchange.com/questions/89625/is-troff-groff-relevant-anymore/"&gt;far less popular&lt;/a&gt;. Using a typesetting system such as groff instead of word processing software has several advantages: saving documents in plain text is memory efficient, easily searchable and scores highly for digital preservation (proprietary word processing file formats tending to eventually obsolesce).&lt;/p&gt;

&lt;p&gt;Below is a worked example on how to use groff to produce a formal letter on Linux.  I'm using groff version 1.22.4, Manjaro Linux 21.0.7 and Zathura 0.4.7.&lt;/p&gt;

&lt;h2&gt;
  
  
  The letter
&lt;/h2&gt;

&lt;p&gt;Open a file titled &lt;em&gt;complaint.me&lt;/em&gt; and copy / paste:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.wh 2c hd \" Top margin (header) set to 2cm
.wh -2c fo \" Bottom margin (footer) set to 2cm
.po 2c \" Right margin set to 2cm
.ll 17c \" Line length set to 17 cm
.in 13c \" Address block indented 13 cm
12 High Street 
.br \" Line break
Leicester
.br
LS1 1AB
.br
01234 567890
.br
another@example.org
.sp \" Spaces one line
8th July 2021
.sp
.in 0 \" Address block indent removed
SuperFibre Broadband
.br
Basildon
.br
PO Box 200
.sp
Dear Sir / Madam,
.pp \" Paragraph start
I am outraged and upset with your response\** \" Places footnote
.(f \" Footnote start
\** Reference C/A/O123 dated 1st July 2021.
.)f \" Footnote end
to my recent complaint.  To suggest that 
.i "my chickens" \" Italics
pecking through 
.i "your broadband wire"
was an event outwith my contract with you is ludicrous.  
Any reputable communications company would ensure that their equipment had been through a testing process which ensures 
.b "fair wear and tear"  \" Bold
caused by farmyard animals.
.pp
I insist that you release me from the remaining twelve months of my broadband contract immediately, and furthermore compensate me for the distress caused to my poultry.
.sp 2 \" Spaces two lines
Yours faithfully,
.sp 2 
A N Other
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  View on screen with Zathura
&lt;/h2&gt;

&lt;p&gt;Assuming you have the &lt;a href="https://pwmt.org/projects/zathura/"&gt;Zathura&lt;/a&gt; document viewer installed at the terminal type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;groff &lt;span class="nt"&gt;-me&lt;/span&gt; &lt;span class="nt"&gt;-dpaper&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;a4 complaint.me | zathura -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command pipes postscript into Zathura's standard input with the following result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.preciouschicken.com/blog/images/groff-letter.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZsfRXB5p--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.preciouschicken.com/blog/images/groff-letter-thumb.png" alt="A letter of complaint written in groff" width="630" height="827"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Output directly to print
&lt;/h2&gt;

&lt;p&gt;Assuming a &lt;a href="https://www.mattcutts.com/blog/change-default-printer-linux-firefox/"&gt;default printer has been set&lt;/a&gt;, then we can also send the postcript directly to the printer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;groff &lt;span class="nt"&gt;-me&lt;/span&gt; &lt;span class="nt"&gt;-dpaper&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;a4 complaint.me | lp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As with the previous command this instructs groff to format for A4 sized paper.&lt;/p&gt;

&lt;h2&gt;
  
  
  Output pdf file
&lt;/h2&gt;

&lt;p&gt;The previous commands avoided the use of PDF all together, but if you would rather save in that format, then adding the &lt;em&gt;-T&lt;/em&gt; flag indicates you want the output something other than default postscript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;groff &lt;span class="nt"&gt;-me&lt;/span&gt; &lt;span class="nt"&gt;-dpaper&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;a4 &lt;span class="nt"&gt;-T&lt;/span&gt; pdf complaint.me &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; complaint.pdf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Me, me, me?
&lt;/h2&gt;

&lt;p&gt;As groff is very low-level then macro facilities are grouped together into packages to allow routine operations (such as footnotes) to be done efficiently.  &lt;em&gt;Me&lt;/em&gt; is one of these packages, although &lt;a href="https://www.stephenlindholm.com/groff_macros.html"&gt;far from the only option&lt;/a&gt;; in fact a new macro package, &lt;em&gt;&lt;a href="http://ankarstrom.se/~john/mk.html"&gt;-mk&lt;/a&gt;&lt;/em&gt;, was released this very month.  The &lt;em&gt;-me&lt;/em&gt; flag on the command line informs groff this notation is being used, likewise the &lt;a href="https://man7.org/linux/man-pages/man5/groff_filenames.5.html"&gt;recommended file extension&lt;/a&gt; to use is &lt;em&gt;.me&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  vim-groff-viewer
&lt;/h2&gt;

&lt;p&gt;If you are a Vim / Neovim user then the &lt;a href="https://github.com/PreciousChicken/vim-groff-viewer"&gt;vim-groff-viewer&lt;/a&gt; plugin wraps these commands into a couple of key mappings so you don't have to leave the comfort of Vim for the command line.&lt;/p&gt;

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

&lt;p&gt;I guess the one obvious question is why use groff at all, as opposed to LaTeX?    Possibly because there is something satisfying about learning modern uses for an application whose lineage dates back to 1964.   Or maybe just, to paraphrase George Mallory, because it is there?  Thoughts, observations?  Feel free to add below.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Further References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://opensource.com/article/18/2/how-format-academic-papers-linux-groff-me"&gt;How to format academic papers on Linux with groff -me&lt;/a&gt;.  Gentle introduction to groff and the &lt;em&gt;-me&lt;/em&gt; package.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.freebsd.org/44doc/usd/19.memacros/paper.pdf"&gt;Writing Papers with NROFF using −me&lt;/a&gt;.  Introductory paper written by Eric P. Allman, author of the &lt;em&gt;-me&lt;/em&gt; package.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.freebsd.org/44doc/usd/20.meref/paper.pdf"&gt;&lt;em&gt;-me&lt;/em&gt; Reference Manual&lt;/a&gt;.  Full reference for &lt;em&gt;-me&lt;/em&gt;, again written by Allman.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://man7.org/linux/man-pages/man7/groff_me.7.html"&gt;groff_me man page&lt;/a&gt;.  Linux groff_me(7) Miscellaneous Information Manual.&lt;/li&gt;
&lt;li&gt; &lt;a href="https://videos.lukesmith.xyz/videos/watch/6e8047a6-a940-481b-803c-6fc13fa22eb9"&gt;groff/troff: MUH MINIMALIST Documents&lt;/a&gt; Luke Smith's introductory video to groff, though uses the -ms package rather than &lt;em&gt;-me&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.gnu.org/software/groff/manual/groff.html"&gt;The GNU Troff Manual&lt;/a&gt;.  The definitive groff manual.  However only one sentence on the &lt;em&gt;-me&lt;/em&gt; package.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>groff</category>
      <category>linux</category>
      <category>latex</category>
    </item>
    <item>
      <title>TiddlyWiki5, Raspberry Pi and Vim: A guide for the command line aficionado</title>
      <dc:creator>Precious Chicken</dc:creator>
      <pubDate>Sat, 15 May 2021 21:03:28 +0000</pubDate>
      <link>https://dev.to/preciouschicken/tiddlywiki5-raspberry-pi-and-vim-a-guide-for-the-command-line-aficionado-1khd</link>
      <guid>https://dev.to/preciouschicken/tiddlywiki5-raspberry-pi-and-vim-a-guide-for-the-command-line-aficionado-1khd</guid>
      <description>&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/2bvEY5Ru5ZQ"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;The practice of personal information management has always left me unsatisfied; a square hole in the puzzle of life that you just don't have a square peg for.  After looking into options for square pegs I've opted for a zettelkasten method implemented via a &lt;a href="https://tiddlywiki.com/"&gt;TiddlyWiki&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I wanted to host this on a Raspberry Pi and access this on all the computers on my local network (e.g. tablets, phones, etc).  However I also wanted the ability to directly edit tiddlers (the basic unit of information in TiddlyWiki) using neovim, so I don't always have to go through a browser.  Seen as I was using neovim I also wanted to be able to initiate a tiddler from the command line - copying a title in from whatever I'm reading and having it CamelCase create a tiddler in Tiddlywiki as per the above video.&lt;/p&gt;

&lt;p&gt;This proved problematic, which I explain in the long-winded and unnecessary background, so I thought worth recording the steps I took on both the Raspberry Pi and my Linux box below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Long-winded and unnecessary background
&lt;/h2&gt;

&lt;p&gt;This turned out to be more difficult than I thought.  The &lt;a href="https://github.com/Jermolene/TiddlyWiki5"&gt;vanilla node.js installation&lt;/a&gt; of TiddlyWiki doesn't play well with direct editing of tiddlers, i.e. editing the tiddlers requires a manual node restart before it appears in the browser.  &lt;a href="https://ooktech.com/"&gt;OokTech&lt;/a&gt; has an &lt;a href="https://github.com/OokTech/TW5-BobEXE"&gt;executable&lt;/a&gt; that does allow direct editing - but this &lt;a href="https://github.com/OokTech/TW5-BobEXE/issues/18"&gt;doesn't work&lt;/a&gt; on a Raspberry Pi.  Not to fear however as OokTech also have a &lt;a href="https://github.com/OokTech/TW5-Bob"&gt;plugin&lt;/a&gt; which gives the same functionality.&lt;/p&gt;

&lt;p&gt;So I used this for a number of months - however there was a snag.  Every so often a tiddler would appear to save correctly after a browser edit, but on return the changes would have dissapeared.  Not knowing exactly what the problem was my first thought was to upgrade to the latest version, in case this solved the problem&lt;sup id="fnref1"&gt;1&lt;/sup&gt;.  So I updated both TiddlyWiki5 and the OokTech plugin; at which point it stopped working altogether.&lt;/p&gt;

&lt;p&gt;Rather than starting over again I wondered if there might be another way.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Raspberry Pi
&lt;/h2&gt;

&lt;p&gt;First the steps you need to take with the Pi.  This assumes you've installed the standard Pi OS (see Version control for the software used).&lt;/p&gt;

&lt;h3&gt;
  
  
  1a. Enable Port 8080
&lt;/h3&gt;

&lt;p&gt;As &lt;a href="https://www.preciouschicken.com/blog/posts/node-tiddlywiki5-raspberry-pi-port-8080/"&gt;blogged about previously&lt;/a&gt;, if you are using the default &lt;a href="https://www.raspberrypi.org/software/"&gt;Raspberry Pi OS&lt;/a&gt; ports are shut down by default.  These therefore need to be opened by entering the following into the Pi terminal:&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;ufw
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow ssh
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw &lt;span class="nb"&gt;enable
sudo &lt;/span&gt;ufw allow 8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This installs &lt;a href="https://en.wikipedia.org/wiki/Uncomplicated_Firewall"&gt;Uncomplicated Firewall&lt;/a&gt; onto your Pi, enables it and then allows the port that your TiddlyWiki will be listening on.&lt;/p&gt;

&lt;h3&gt;
  
  
  1b. Install TiddlyWiki5 and Nodemon
&lt;/h3&gt;

&lt;p&gt;At the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;tiddlywiki nodemon &lt;span class="nt"&gt;-g&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-g&lt;/code&gt; flag installs the software globally, rather than in a particular folder.&lt;/p&gt;

&lt;h3&gt;
  
  
  1c. Initiate a wiki
&lt;/h3&gt;

&lt;p&gt;We now use the TiddlyWiki software just installed to initiate a fresh wiki.  At the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tiddlywiki wiki &lt;span class="nt"&gt;--init&lt;/span&gt; server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a directory called &lt;em&gt;wiki&lt;/em&gt; which includes server-related components.  You can change the name of the directory to whatever you wish, but the remainder of the instructions assume the directory is called &lt;em&gt;wiki&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  1d. Find your IP
&lt;/h3&gt;

&lt;p&gt;Now we have to &lt;a href="https://www.raspberrypi.org/documentation/remote-access/ip-address.md"&gt;find the IP address of your Raspberry Pi&lt;/a&gt; on your local network and make a note of it.  For the purposes of this tutorial we are going to say it is &lt;code&gt;192.168.0.12&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  1e. Starting TiddlyWiki with nodemon
&lt;/h3&gt;

&lt;p&gt;Ok, so now the fun bit, starting.  If you weren't bothered about editing the tiddlers in a text editor you could just simply go for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tiddlywiki wiki &lt;span class="nt"&gt;--listen&lt;/span&gt; &lt;span class="nv"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.0.12
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And if you go to &lt;a href="http://192.168.0.12:8080/"&gt;http://192.168.0.12:8080/&lt;/a&gt; in your browser you should see a new wiki.&lt;/p&gt;

&lt;p&gt;All well and good, however this makes editing tiddlers in a text editor awkward.  Using the above method means edits do not get reflected in the browser - or not without manually restarting the node instance of TiddlyWiki.&lt;/p&gt;

&lt;p&gt;This is where &lt;a href="https://nodemon.io/"&gt;nodemon&lt;/a&gt; comes in.  Nodemon watches a directory and restarts a node process any time there is a change in that directory.  It is commonly used for development purposes - if you are working on a JavaScript file you want node to be refreshed and reloaded every time you make a save - and not have to manually restart.  However it can be used to look for changes to tiddlers, rather than code.&lt;/p&gt;

&lt;p&gt;To do so enter at the terminal this intimidatingly long command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nodemon &lt;span class="nt"&gt;--delay&lt;/span&gt; 30 &lt;span class="nt"&gt;-e&lt;/span&gt; tid &lt;span class="nt"&gt;--ignore&lt;/span&gt; &lt;span class="s1"&gt;'wiki/tiddlers/$*.tid'&lt;/span&gt; &lt;span class="nt"&gt;--watch&lt;/span&gt; wiki/tiddlers/ &lt;span class="nv"&gt;$NVM_BIN&lt;/span&gt;/tiddlywiki wiki &lt;span class="nt"&gt;--listen&lt;/span&gt; &lt;span class="nv"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.0.19
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point you can simply walk away leaving the terminal open and running if you have been directly inputting commands into your Raspberry Pi.  If you are accessing your Pi remotely via SSH then close the terminal window itself without exiting the running program (on Manjaro i3 this is done via the &lt;code&gt;mod-Shift-q&lt;/code&gt; keypress, but your distro will likely be different).  This method of exit is required because closing the terminal via standard methods (e.g. &lt;code&gt;ctrl-c&lt;/code&gt; followed by &lt;code&gt;exit&lt;/code&gt;) will likely also kill nodemon.&lt;/p&gt;

&lt;p&gt;So what does this do?  The flags / arguments to nodemon are as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; &lt;code&gt;--delay 30&lt;/code&gt; ensures that after a change nodemon waits 30 seconds before restarting.  If this did not occur then the server would be constantly restarting.&lt;/li&gt;
&lt;li&gt; &lt;code&gt;e tid&lt;/code&gt; tells nodemon that it needs to look for changes in files ending in &lt;code&gt;tid&lt;/code&gt;, that is in tiddlers.&lt;/li&gt;
&lt;li&gt; &lt;code&gt;--ignore 'wiki/tiddlers/$*.tid'&lt;/code&gt; ignores tiddlers that are generated by the system (which by convention start with &lt;code&gt;$&lt;/code&gt;) and not the user.&lt;/li&gt;
&lt;li&gt; &lt;code&gt;--watch wiki/tiddlers/&lt;/code&gt; tells nodemon the folder to watch where changes will happen.&lt;/li&gt;
&lt;li&gt; &lt;code&gt;$NVM_BIN/tiddlywiki wiki --listen host=192.168.0.19&lt;/code&gt; lastly this is the previous command as at the start of the section, but as we aren't running the command via node we have to explicitly tell nodemon where to find the tiddlywiki executable (in &lt;em&gt;$NVM_BIN&lt;/em&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Desktop / Local machine
&lt;/h2&gt;

&lt;p&gt;Switching back from the Raspberry Pi to your Linux machine, the following will allow (relatively) seamless editing in vim / neovim.&lt;/p&gt;

&lt;p&gt;These steps assume you have generated SSH keys allowing you to access your Pi from your Linux machine - if not follow DigitalOcean's excellent &lt;a href="https://www.digitalocean.com/community/tutorials/how-to-set-up-ssh-keys-2"&gt;How To Set Up SSH Keys&lt;/a&gt; tutorial before anything else.&lt;/p&gt;

&lt;h3&gt;
  
  
  2a. Mapping a drive
&lt;/h3&gt;

&lt;p&gt;So that we have the tiddlers accessible on our local machine, the &lt;em&gt;wiki&lt;/em&gt; directory on the Pi needs to be mounted.  Although the actual mount command is given in the &lt;em&gt;tw&lt;/em&gt; shell script below, some initial steps need to be taken for this to work.  &lt;/p&gt;

&lt;p&gt;First create the mountpoint (e.g. where on your local system you want the tiddlers to appear):&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; ~/wiki
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we need to install &lt;em&gt;sshfs&lt;/em&gt; which allows us to mount a drive over SSH.  I'm using Manjaro so it is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pamac &lt;span class="nb"&gt;install &lt;/span&gt;sshfs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are using Ubuntu or derivative then it will be &lt;code&gt;sudo apt install sshfs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To make things a bit easier once we've decided where our tiddlers are mounted we will set this path as an environment variable so we aren't having to retype the path again.  We do this by editing our &lt;code&gt;~/.bash_profile&lt;/code&gt; (or whatever shell we use - I use zsh so it is &lt;code&gt;~/.zshenv&lt;/code&gt;)&lt;sup id="fnref2"&gt;2&lt;/sup&gt; to include:&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;export &lt;/span&gt;&lt;span class="nv"&gt;TIDDLYWIKIPATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/wiki/tiddlers/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2b. Vim plugin
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://github.com/sukima/vim-tiddlywiki"&gt;vim-tiddlywiki&lt;/a&gt; plugin is absolutely outstanding for managing tiddlers in Vim.  If you don't use a plugin manager, install it into Neovim as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; git clone &lt;span class="nt"&gt;--depth&lt;/span&gt; 1 https://github.com/sukima/vim-tiddlywiki ~/.config/nvim/pack/sukima/start/vim-tiddlywiki
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are using original Vim rather than Neovim, then the path above will need amending as Vim does not use the &lt;em&gt;.config&lt;/em&gt; directory.  Or simply use a plugin manager.&lt;/p&gt;

&lt;p&gt;This plugin not only means that the tiddler format is recognised, but also allows you to create new tiddlers with the right metadata in place and jump to the other tiddlers using CamelCase links.&lt;/p&gt;

&lt;p&gt;For full use you will also need to let the plugin know where you keep your tiddlers by adding the following line to your &lt;em&gt;~/.config/nvim/init.vim&lt;/em&gt; or &lt;em&gt;~/.vimrc&lt;/em&gt;, i.e.:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let g:tiddlywiki_dir=$TIDDLYWIKIPATH
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2c.  Command line editing
&lt;/h3&gt;

&lt;p&gt;To make the process buttery smooth I want to automate my workflow.  This being: read an article on the net, copy/paste the title of the article and have neovim open a tiddler already named using CamelCase.  So for example, let's say I want to take some notes on Hayek's paper on open markets: &lt;a href="https://doi.org/10.1142/9789812701275_0025"&gt;The use of knowledge in society&lt;/a&gt;.  I want to open a terminal, type &lt;code&gt;tw The use of knowledge in society&lt;/code&gt; and start editing a tiddler named TheUseOfKnowledgeInSociety.tid in neovim.&lt;/p&gt;

&lt;p&gt;Create a file named &lt;em&gt;tw&lt;/em&gt; wherever you keep user-specific executable files (so for me this is &lt;em&gt;~/.local/bin/tw&lt;/em&gt;, but if you are using Ubuntu it will likely be &lt;em&gt;~/bin/tw&lt;/em&gt;) and copy / paste the following code.  Alternatively you can download from the &lt;a href="https://github.com/PreciousChicken/tiddlywiki5-raspberry-pi-guide"&gt;github repo&lt;/a&gt;.  This code assumes that you've created your mount point at &lt;em&gt;~/wiki&lt;/em&gt; as above, if not then you will need to change the sshfs line.&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;#!/bin/bash&lt;/span&gt;

&lt;span class="c"&gt;# Opens a new tiddlywiki tiddler named after arguments that follow commands&lt;/span&gt;
&lt;span class="c"&gt;# Uses vim-tiddlywiki plugin to create metadata&lt;/span&gt;
&lt;span class="c"&gt;# https://www.preciouschicken.com/blog/posts/tiddlywiki5-raspberry-pi-guide/&lt;/span&gt;

&lt;span class="c"&gt;# Checks if pi is mounted, if not mounts in folder created earlier&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;mount | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'pi@'&lt;/span&gt;| &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; 0 &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;sshfs pi@192.168.0.19:/home/pi/wiki &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="nv"&gt;$TIDDLYWIKIPATH&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; reconnect
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Replaces non-alphanumeric characters in arguments&lt;/span&gt;
&lt;span class="nv"&gt;arguments&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$@&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s2"&gt;"s/[’]//g"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;arguments&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$arguments&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/[^a-zA-Z0-9]/ /g'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# CamelCase converts all arguments&lt;/span&gt;
&lt;span class="nv"&gt;tid_title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;word &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;$arguments&lt;/span&gt;
&lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c"&gt;# Changes argument to lowercase&lt;/span&gt;
    &lt;span class="nv"&gt;lowercasevar&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;word&lt;/span&gt;&lt;span class="p"&gt;,,&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
    &lt;span class="c"&gt;# Capitalises first character of argument&lt;/span&gt;
    &lt;span class="nv"&gt;sentencevar&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;lowercasevar&lt;/span&gt;&lt;span class="p"&gt;^&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
    tid_title+&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$sentencevar&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;

&lt;span class="c"&gt;# Opens neovim with correct tiddler name&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;$TIDDLYWIKIPATH$tid_title&lt;/span&gt;&lt;span class="s1"&gt;'.tid'&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;
&lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="c"&gt;# Updates metadata if tiddler exists&lt;/span&gt;
    nvim &lt;span class="nt"&gt;-c&lt;/span&gt; TiddlyWikiUpdateMetadata &lt;span class="nv"&gt;$TIDDLYWIKIPATH$tid_title&lt;/span&gt;&lt;span class="s1"&gt;'.tid'&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="c"&gt;# Creates tiddler if not found&lt;/span&gt;
    nvim &lt;span class="nt"&gt;-c&lt;/span&gt; TiddlyWikiInitializeTemplate &lt;span class="nv"&gt;$TIDDLYWIKIPATH$tid_title&lt;/span&gt;&lt;span class="s1"&gt;'.tid'&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then make this 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 ~/.local/bin/tw
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On next restart of terminal, typing &lt;code&gt;tw The use of knowledge in society&lt;/code&gt; should create a new tiddler ready for us to type.  If the tiddler already exists then the shell script will recognise that and update the metadata instead.&lt;/p&gt;

&lt;p&gt;The shell script does its best to cope with non-alphanumeric characters.  So apostrophes are deleted (&lt;em&gt;We're not really strangers&lt;/em&gt; gets changed to &lt;em&gt;WereNotReallyStrangers&lt;/em&gt;) while it substitutes other non-alphanumerics characters for a space (so &lt;em&gt;Knee-deep in the Big Muddy&lt;/em&gt; ends up as &lt;em&gt;KneeDeepInTheBigMuddy&lt;/em&gt;).  I'm sure there will be edge cases which will not work out - feel free to comment below.&lt;/p&gt;

&lt;h2&gt;
  
  
  A note on spawning
&lt;/h2&gt;

&lt;p&gt;It is worth noting that nodemon can be a bit difficult to stop once it is started.  One option is to find the process running and stop them individually, to quote &lt;a href="https://linustechtips.com/topic/1049531-nodejs-how-to-stop-nodemon/"&gt;one particular solution&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ps aux | grep -i nodemon, find out which process number nodemon is, then issue a kill -9 [process ID]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Or alternatively reboot the Pi.  That can be much easier...&lt;/p&gt;

&lt;h2&gt;
  
  
  Version control
&lt;/h2&gt;

&lt;p&gt;If this doesn't work for you, it might be due to version conflicts.  At the time of writing I was using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pi: Raspberry Pi 3 Model B Rev 1.2 running Raspbian GNU/Linux 10 (buster) armv7l.  Node v14.11.0, npm v6.14.8.  TiddlyWiki v5.1.23.&lt;/li&gt;
&lt;li&gt;Desktop: Manjaro Linux 21.0.7 Omara.  zsh v5.8. neovim v0.4.4.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;This is not perfect - for instance there is a 30 second delay between creating a tiddler on the command line and it being reflected in the browser.  And there are probably all sorts of circumstances where the bash script will not work out.  Square peg in a square hole or Linux kludge?  Comments, feedback, etc below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Addendum: Other options
&lt;/h2&gt;

&lt;p&gt;A thread on the &lt;a href="https://groups.google.com/g/tiddlywiki/c/gssKE66gPv0"&gt;TiddlyWiki mailing list&lt;/a&gt;, subsequent to this post, pointed out a number of alternatives to using nodemon:&lt;/p&gt;

&lt;h3&gt;
  
  
  watch-fs plugin
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://github.com/linonetwo/tiddlywiki-plugins/tree/master/plugins/linonetwo/watch-fs"&gt;watch-fs&lt;/a&gt; TiddlyWiki plugin states that it "enables TiddlyWiki to watch the change in your disk, and if you edit one of your tiddler using editor likes VSCode and save it on the disk, the change will immediately reflected in the browser."  &lt;/p&gt;

&lt;h3&gt;
  
  
  TiddlyWiki API
&lt;/h3&gt;

&lt;p&gt;An option suggested by &lt;a href="https://github.com/saqimtiaz"&gt;Saq Imtiaz&lt;/a&gt; is instead of saving newly created tiddler files directly into the wiki directory, one could save the tiddler with a PUT request via cURL using the &lt;a href="https://tiddlywiki.com/#WebServer%20API%3A%20Put%20Tiddler:%5B%5BWebServer%20API%3A%20Put%20Tiddler%5D%5D"&gt;TiddlyWiki API&lt;/a&gt;; for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; PUT &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'http://192.168.0.12:8080/recipes/default/tiddlers/NewTiddlerTitle'&lt;/span&gt; &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{
 "tags": "firstTag anotherTag",
 "creator": "gene",
 "modifier": "gene",
 "text": "The use of knowledge in society"
}'&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-Requested-With: TiddlyWiki"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using this example as a basis a Vim plugin or a shell script called by Vim could be written.&lt;/p&gt;

&lt;h3&gt;
  
  
  TW5-Bob plugin
&lt;/h3&gt;

&lt;p&gt;Although as previously covered this did not quite work out for me, the &lt;a href="https://github.com/OokTech/TW5-Bob"&gt;TW5-Bob&lt;/a&gt; plugin is also an alternative given that it offers "two-way real-time syncing between the browser and file system".&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;For clarity I suspect the problem was something to do with how I had installed or configured Bob, rather than an error in the system itself.  But a fresh install is never a bad idea anyway. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;You could also use your &lt;em&gt;~/.bashrc&lt;/em&gt; or &lt;em&gt;~/.zshrc&lt;/em&gt;, but I think the profile / env files are &lt;a href="https://unix.stackexchange.com/a/71258"&gt;preferable&lt;/a&gt;. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>tiddlywiki</category>
      <category>vim</category>
      <category>raspberrypi</category>
      <category>bash</category>
    </item>
  </channel>
</rss>
