Congratulations!

[Valid Atom 1.0] This is a valid Atom 1.0 feed.

Recommendations

This feed is valid, but interoperability with the widest range of feed readers could be improved by implementing the following recommendations.

Source: https://bret.io/feed.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <feed xmlns="http://www.w3.org/2005/Atom">
  3.  <title>bret.io</title>
  4.  <id>https://bret.io/feed.xml</id>
  5.  <updated>2024-02-13T18:24:49.707Z</updated>
  6.  <link rel="self" type="application/atom+xml" href="https://bret.io/feed.xml"/>
  7.  <link rel="alternate" type="application/json" href="https://bret.io/feed.json"/>
  8.  <link rel="alternate" type="text/html" href="https://bret.io"/>
  9.  <author>
  10.    <name>Bret Comnes</name>
  11.    <uri>https://bret.io</uri>
  12.  </author>
  13.  <generator uri="https://github.com/bcomnes/jsonfeed-to-atom#readme" version="1.2.5">jsonfeed-to-atom</generator>
  14.  <rights>© 2024 Bret Comnes</rights>
  15.  <subtitle>A running log of announcements, projects and accomplishments.</subtitle>
  16.  <entry>
  17.    <id>https://bret.io/blog/2024/the-art-of-doing-science-and-engineering/#2024-02-13T18:24:49.707Z</id>
  18.    <title>The Art of Doing Science and Engineering</title>
  19.    <updated>2024-02-13T18:24:49.707Z</updated>
  20.    <published>2024-02-13T18:24:49.707Z</published>
  21.    <author>
  22.      <name>Bret Comnes</name>
  23.      <uri>https://bret.io</uri>
  24.    </author>
  25.    <content type="html"><![CDATA[<figure>
  26.  <a href="./img/cover.jpeg">
  27.    <img loading="auto" src="./img/cover.jpeg" alt="Book Cover">
  28.  </a>
  29.  <figcaption>The art of doing science and engineering : Learning to Learn by Richard W. Hamming</figcaption>
  30. </figure>
  31. <p>Richard Hamming’s “The Art of Doing Science and Engineering” is a book capturing the lessons he taught in a course he gave at the U.S. Navy Postgraduate School in Monterey, CA.
  32. He characterizes what he was trying to teach was “style” of thinking in science and engineering.</p>
  33. <p>Having a physics degree myself, and also finding myself periodically ruminating on the agony of professional software development and hoping to find some overlap between my professional field and Hamming’s life experience, I gave it a read.</p>
  34. <p>The book is filled with nuggets of wisdom and illustrates a career of move-the-needle science and engineering at Bell Labs.
  35. I didn’t personally find much value in many of the algebraic walk-through’s of various topics like information theory, but learning about how Hamming discovered error correcting codes definitely was interesting and worth a read.</p>
  36. <p>The highlight of book comes in the second half where he includes interesting stories, analogies and observations on nearly every page. Below are my highlights I pulled while reading.</p>
  37. <h2 id="on-what-makes-good-design" tabindex="-1">On What Makes Good Design</h2>
  38. <blockquote>
  39. <p>That brings up another point, which is now well recognized in software for computers but which applies to hardware too. Things change so fast that part of the system design problem is that the system will be constantly upgraded in ways you do not now know in any detail! Flexibility must be part of the modern design of things and processes. Flexibility built into the design means not only will you be better able to handle the changes which will come after installation, but it also contributes to your own work as the small changes which inevitably arise both in the later stages of design and in the field installation of the system…</p>
  40. <p>Thus rule two:</p>
  41. <p><strong>Part of systems engineering design is to prepare for changes so they can be gracefully made and still not degrade the other parts.</strong></p>
  42. <p>– p.367</p>
  43. </blockquote>
  44. <p>This quote is my favorite out of the entire book.
  45. It feels like a constant fight in software engineering between the impulse to lock down runtime versions, specific dependency versions, and other environmental factors versus developing software in such a way that accommodates wide variance in all of these different component factors.
  46. Both approaches argue reliability and flexibility, however which approach actually tests for it?</p>
  47. <p>In my experience, the tighter the runtime dependency specifications, the faster fragility spreads, and it’s satisfying to hear Hamming’s experience echo this observation. Sadly though, his observation that those writing software will universally understand this simply hasn’t held up.</p>
  48. <blockquote>
  49. <p>Good design protects you from the need for too many highly accurate components in the system. But such design principals are still, to this date, ill understood and need to be researched extensively. Not that good designers do not understand this intuitively, merely it is not easily incorporated into the design methods you were thought in school.</p>
  50. <p>Good minds are still need in spite of all the computing tools we have developed. The best mind will be the one who gets the principle into the design methods taught so it will be automatically available for lesser minds!.</p>
  51. <p>– p.268</p>
  52. </blockquote>
  53. <p>Here Hamming is describing H.S. Black’s feedback circuit’s tolerance for low accuracy components as what constitutes good design. I agree! Technology that works at any scale, made out of commodity parts with minimal runtime requirements tends to be what is most useful across the longest amount of time.</p>
  54. <h2 id="on-committees" tabindex="-1">On Committees</h2>
  55. <blockquote>
  56. <p>Committee decisions, which tend to diffuse responsibility, are seldom the best in practice—most of the time they represent a compromise which has none of the virtues of any path and tends to end in mediocrity.</p>
  57. <p>– p.274</p>
  58. </blockquote>
  59. <p>I appreciated his observations on committees, and their tendency to launder responsibility.
  60. They serve a purpose, but its important to understand their nature.</p>
  61. <h2 id="on-data-and-observation" tabindex="-1">On Data and Observation</h2>
  62. <blockquote>
  63. <p>The Hawthorne effect strongly suggests the proper teaching method will always to be in a state of experimental change, and it hardly matters just what is done; all that matters is both the professor and the students believe in the change.</p>
  64. <p>– p.288</p>
  65. </blockquote>
  66. <blockquote>
  67. <p>It has been my experience, as well as the experience of many others who have looked, that data is generally much less accurate than it is advertised to be. This is not a trivial point—we depend on initial data for many decisions, as well as for the input data for simulations which result in decisions.</p>
  68. <p>– p.345</p>
  69. </blockquote>
  70. <blockquote>
  71. <p>Averages are meaningful for homogeneous groups (homogeneous with respect to the actions that may later be taken), but for diverse groups averages are often meaningless. As earlier remarked, the average adult has one breast and one testicle, but that does not represent the average person in our society.</p>
  72. <p>– p.356</p>
  73. </blockquote>
  74. <blockquote>
  75. <p>You may think the title means that if you measure accurately you will get an accurate measurement, and if not then not, but it refers to a much more subtle thing—the way you choose to measure things controls to a large extent what happens. I repeat the story Eddington told about the fishermen who went fishing with a net. They examined the size of the fish they caught and concluded there was a minimum size to the fish in the sea. The instrument you use clearly affects what you see.</p>
  76. <p>– p.373</p>
  77. </blockquote>
  78. <p>Intuitively I think many people who attempt to measure anything understand that their approach reflects in the results to some degree.
  79. I hadn’t heard of the <a href="https://en.wikipedia.org/wiki/Hawthorne_effect">Hawthorne effect</a> before, but intuitively it makes sense.</p>
  80. <p>People with an idea on how to improve something implement their idea and it works, because they want it to work and allow the effects to be fully effective.
  81. Someone else is prescribed this idea or brought into the fold where the idea is implemented and the benefits of the idea evaporate.</p>
  82. <p>I’ve long suspected that in the context of professional software development, where highly unscrutinized benchmarks and soft data are the norm, people start with an opinion or theory and work back to data that supports it.
  83. Could it just be that people need to believe that working in a certain way is necessary for them to work optimally? Could it be “data” is often just a work function used to out maneuver competing ideas?</p>
  84. <p>Anyway, just another thing to factor for when data is plopped in your lap.</p>
  85. <h2 id="on-theory" tabindex="-1">On Theory</h2>
  86. <blockquote>
  87. <p>Moral: there need not be a unique form of a theory to account for a body of observations; instead, two rather different-looking theories can agree on all the predicted details. You cannot go from a body of data to a unique theory! I noted this in the last chapter.</p>
  88. <p>–p.314</p>
  89. </blockquote>
  90. <blockquote>
  91. <p>Heisenberg derived the uncertainty principle that conjugate variables, meaning Fourier transforms, obeyed a condition in which the product of the uncertainties of the two had to exceed a fixed number, involving Planck’s constant. I earlier commented, Chapter 17, this is a theorem in Fourier transforms-any linear theory must have a corresponding uncertainty principle, but among physicists it is still widely regarded as a physical effect from nature rather than a mathematical effect of the model.</p>
  92. <p>–p.316</p>
  93. </blockquote>
  94. <p>I appreciate Hamming suggesting that some of our understanding of physical reality could be a byproduct of the model being used to describe it.
  95. It’s not exactly examined closely in undergraduate or graduate quantum mechanics, and I find it interesting Hamming, who’s clearly highly intuitive with modeling, also raises this question.</p>
  96. <h2 id="predictions" tabindex="-1">Predictions</h2>
  97. <blockquote>
  98. <p>Let me now turn to predictions of the immediate future. It is fairly clear that in time “drop lines” from the street to the house (they may actually be buried, but will probably still be called “drop lines”) will be fiber optics. Once a fiber-optic wire is installed, then potentially you have available almost all the information you could possibly want, including TV and radio, and possibly newspaper articles selected according to your interest profile (you pay the printing bill which occurs in your own house). There would be no need for separate information channels most of the time. At your end of the fiber there are one or more digital filters. Which channel you want, the phone, radio, or TV, can be selected by you much as you do now, and the channel is determined by the numbers put into the digital filter-thus the same filter can be multipurpose, if you wish. You will need one filter for each channel you wish to use at the same time (though it is possible a single time-sharing filter would be available) and each filter would be of the same standard design. Alternately, the filters may come with the particular equipment you buy.</p>
  99. <p>– p.284-285</p>
  100. </blockquote>
  101. <p>Here Hamming is predicting the internet. He got very close, and it’s interesting to think that these signals would all just be piped to your house in a bundle you you pay for a filter to unlock access to the ones you want. Hey Cable TV worked that for a long time!</p>
  102. <h2 id="on-leadership" tabindex="-1">On Leadership</h2>
  103. <blockquote>
  104. <p>But a lot of evidence on what enabled people to make big contributions points to the conclusion that a famous prof was a terrible lecturer and the students had to work hard to learn it for themselves! I again suggest a rule:</p>
  105. <p><strong>What you learn from others you can use to follow;</strong></p>
  106. <p><strong>What you learn for yourself you can use to lead.</strong></p>
  107. <p>– p.292</p>
  108. </blockquote>
  109. <p>Learn by doing, not by following.</p>
  110. <blockquote>
  111. <p><strong>What you did to become successful is likely to be counterproductive when applied at a later date.</strong></p>
  112. <p>– p.342</p>
  113. </blockquote>
  114. <p>It’s easy to blame changing trends in software development for the disgustingly short half-life of knowledge regarding development patterns and tools, but I think it’s probably just the nature of knowledge based work.
  115. Operating by yourself may be effective and work well, but its not a recipe for success at any given moment in time.</p>
  116. <blockquote>
  117. <p>A man was examining the construction of a cathedral. He asked a stonemason what he was doing chipping the stones, and the mason replied, “I am making stones.” He asked a stone carver what he was doing; “I am carving a gargoyle.” And so it went; each person said in detail what they were doing. Finally he came to an old woman who was sweeping the ground. She said, “I am helping build a cathedral.”
  118. If, on the average campus, you asked a sample of professors what they were going to do in the next class hour, you would hear they were going to “teach partial fractions,” “show how to find the moments of a normal distribution,” “explain Young’s modulus and how to measure it,” etc. I doubt you would often hear a professor say, “I am going to educate the students and prepare them for their future careers.”
  119. This myopic view is the chief characteristic of a bureaucrat. To rise to the top you should have the larger view—at least when you get there.</p>
  120. <p>– p.360</p>
  121. </blockquote>
  122. <p>Software bureaucrats aplenty. Really easy to fall into this role.</p>
  123. <blockquote>
  124. <p>I must come to the topic of “selling” new ideas. You must master three things to do this (Chapter 5):</p>
  125. <ol>
  126. <li>Giving formal presentations,</li>
  127. <li>Producing written reports, and</li>
  128. <li>Mastering the art of informal presentations as they happen to occur.</li>
  129. </ol>
  130. <p>All three are essential—you must learn to sell your ideas, not by propaganda, but by force of clear presentation. I am sorry to have to point this out; many scientists and others think good ideas will win out automatically and need not be carefully presented. They are wrong;</p>
  131. <p>– p.396</p>
  132. </blockquote>
  133. <p>One thing I regret over the last 10 years of my career is not writing down more insights I have learned through experience.
  134. Ideas simply don’t transmit if they aren’t written down or put into some consumable format like video or audio.
  135. Nearly every annoying tool or developer trend you are forced to use is in play because it communicated the idea through blogs, videos and conference talks.
  136. And those who watched echoed these messages.</p>
  137. <h2 id="on-experts" tabindex="-1">On Experts</h2>
  138. <blockquote>
  139. <p><strong>An expert is one who knows everything about nothing; a generalist knows nothing about everything.</strong></p>
  140. <p>In an argument between a specialist and a generalist, the expert usually wins by simply (1) using unintelligible jargon, and (2) citing their specialist results, which are often completely irrelevant to the discussion. The expert is, therefore, a potent factor to be reckoned with in our society. Since experts both are necessary and also at times do great harm in blocking significant progress, they need to be examined closely. All too often the expert misunderstands the problem at hand, but the generalist cannot carry though their side to completion. The person who thinks they understand the problem and does not is usually more of a curse (blockage) than the person who knows they do not understand the problem.</p>
  141. <p>– p.333</p>
  142. </blockquote>
  143. <p>Understand when you are generalist and a specialist.</p>
  144. <blockquote>
  145. <p>Experts, in looking at something new, always bring their expertise with them, as well as their particular way of looking at things. Whatever does not fit into their frame of reference is dismissed, not seen, or forced to fit into their beliefs. Thus really new ideas seldom arise from the experts in the field. You cannot blame them too much, since it is more economical to try the old, successful ways before trying to find new ways of looking and thinking.</p>
  146. <p><strong>If an expert says something can be done he is probably correct, but if he says it is impossible then consider getting another opinion.</strong></p>
  147. <p>– p.336</p>
  148. </blockquote>
  149. <p>Anyone wading into a technical field will encounter experts at every turn.
  150. They have valuable information, but they are also going to give you dated, myopic advice (gatekeeping?).
  151. I like Hamming’s framing here and it reflects my experience when weighing expert opinion.</p>
  152. <blockquote>
  153. <p>In some respects the expert is the curse of our society, with their assurance they know everything, and without the decent humility to consider they might be wrong. Where the question looms so important, I suggested to you long ago to use in an argument, “What would you accept as evidence you are wrong?” Ask yourself regularly, “Why do I believe whatever I do?” Especially in the areas where you are so sure you know, the area of the paradigms of your field.</p>
  154. <p>– p.340</p>
  155. </blockquote>
  156. <p>I love this exercise. It will also drive you crazy. Tread carefully.</p>
  157. <blockquote>
  158. <p>Systems engineering is indeed a fascinating profession, but one which is hard to practice. There is a great need for real systems engineers, as well as perhaps a greater need to get rid of those who merely talk a good story but cannot play the game effectively.</p>
  159. <p>– p.372</p>
  160. </blockquote>
  161. <p>Controversial, harsh, but true.</p>
  162. <h2 id="the-binding" tabindex="-1">The Binding</h2>
  163. <p>The last thing I want to recognize is the beautiful cloth resin binding and quality printing of the book. Bravo Stripe Press for still producing beautiful artifacts at affordable pricing in the age of print on demand.</p>
  164. ]]></content>
  165.    <link rel="alternate" href="https://bret.io/blog/2024/the-art-of-doing-science-and-engineering/"/>
  166.  </entry>
  167.  <entry>
  168.    <id>https://bret.io/blog/2024/async-neocities-bin/#2024-01-15T23:55:33.582Z</id>
  169.    <title>async-neocities has a bin</title>
  170.    <updated>2024-01-15T23:55:33.582Z</updated>
  171.    <published>2024-01-15T23:55:33.582Z</published>
  172.    <author>
  173.      <name>Bret Comnes</name>
  174.      <uri>https://bret.io</uri>
  175.    </author>
  176.    <content type="html"><![CDATA[<p><a href="https://github.com/bcomnes/async-neocities"><code>async-neocities</code></a> <a href="https://github.com/bcomnes/async-neocities/releases/tag/v3.0.0">v3.0.0</a> is now available and introduces a CLI.</p>
  177. <pre><code class="hljs language-console">Usage: async-neocities [options]
  178.  
  179.    Example: async-neocities --src public
  180.  
  181.    --help, -h            print help text
  182.    --src, -s             The directory to deploy to neocities (default: &quot;public&quot;)
  183.    --cleanup, -c         Destructively clean up orphaned files on neocities
  184.    --protect, -p         String to minimatch files which will never be cleaned up
  185.    --status              Print auth status of current working directory
  186.    --print-key           Print api-key status of current working directory
  187.    --clear-key           Remove the currently associated API key
  188.    --force-auth          Force re-authorization of current working directory
  189.  
  190. async-neocities (v3.0.0)
  191. </code></pre>
  192. <p>When you run it, you will see something similar to this:</p>
  193. <pre><code class="hljs language-console"><span class="hljs-meta prompt_">&gt; </span><span class="language-bash">async-neocities --src public</span>
  194.  
  195. Found siteName in config: bret
  196. API Key found for bret
  197. Starting inspecting stage...
  198. Finished inspecting stage.
  199. Starting diffing stage...
  200. Finished diffing stage.
  201. Skipping applying stage.
  202. Deployed to Neocities in 743ms:
  203.    Uploaded 0 files
  204.    Orphaned 0 files
  205.    Skipped 244 files
  206.    0 protected files
  207. </code></pre>
  208. <p><a href="https://github.com/bcomnes/async-neocities"><code>async-neocities</code></a> was previously available as a GitHub Action called <a href="https://github.com/marketplace/actions/deploy-to-neocities">deploy-to-neocities</a>. This Action API remains available, however the CLI offers a local-first workflow that was not previously offered.</p>
  209. <h2 id="local-first-deploys" tabindex="-1">Local First Deploys</h2>
  210. <p>Now that <code>async-neocities</code> is available as a CLI, you can easily configure it as an <code>npm</code> script and run it locally when you want to push changes to <a href="https://neocities.org">neocities</a> without relying on GitHub Actions.
  211. It also works great in Actions with side benefit of deploys working exactly the same way in both local and remote environments.</p>
  212. <p>Here is a quick example of that:</p>
  213. <ul>
  214. <li>Install <code>async-neocities@^3.0.0</code> to your project’s <code>package.json</code>.</li>
  215. <li>Set up a <code>package.json</code> deploy script:<pre><code class="hljs language-json"> <span class="hljs-attr">&quot;scripts&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
  216.    <span class="hljs-attr">&quot;build&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;npm run clean &amp;&amp; run-p build:*&quot;</span><span class="hljs-punctuation">,</span>
  217.    <span class="hljs-attr">&quot;build:tb&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;top-bun&quot;</span><span class="hljs-punctuation">,</span>
  218.    <span class="hljs-attr">&quot;clean&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;rm -rf public &amp;&amp; mkdir -p public&quot;</span><span class="hljs-punctuation">,</span>
  219.    <span class="hljs-attr">&quot;deploy&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;run-s build deploy:*&quot;</span><span class="hljs-punctuation">,</span>
  220.    <span class="hljs-attr">&quot;deploy:async-neocities&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;async-neocities --src public --cleanup&quot;</span>
  221.  <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
  222. </code></pre>
  223. </li>
  224. <li>Run a deploy once locally to set up the <code>deploy-to-neocities.json</code> config file. Example config contents:<pre><code class="hljs language-json"><span class="hljs-punctuation">{</span><span class="hljs-attr">&quot;siteName&quot;</span><span class="hljs-punctuation">:</span><span class="hljs-string">&quot;bret&quot;</span><span class="hljs-punctuation">}</span>
  225. </code></pre>
  226. </li>
  227. <li>Run deploys locally with <code>npm run deploy</code>.</li>
  228. <li>Configure your CI to run <code>npm run deploy</code> and configure the token secret.<pre><code class="hljs language-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">to</span> <span class="hljs-string">neociteis</span>
  229.  
  230. <span class="hljs-attr">on:</span>
  231.  <span class="hljs-attr">push:</span>
  232.    <span class="hljs-attr">branches:</span>
  233.      <span class="hljs-bullet">-</span> <span class="hljs-string">master</span>
  234.  
  235. <span class="hljs-attr">env:</span>
  236.  <span class="hljs-attr">node-version:</span> <span class="hljs-number">21</span>
  237.  <span class="hljs-attr">FORCE_COLOR:</span> <span class="hljs-number">2</span>
  238.  
  239. <span class="hljs-attr">concurrency:</span> <span class="hljs-comment"># prevent concurrent deploys doing starnge things</span>
  240.  <span class="hljs-attr">group:</span> <span class="hljs-string">deploy-to-neocities</span>
  241.  <span class="hljs-attr">cancel-in-progress:</span> <span class="hljs-literal">true</span>
  242.  
  243. <span class="hljs-attr">jobs:</span>
  244.  <span class="hljs-attr">deploy:</span>
  245.    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
  246.  
  247.    <span class="hljs-attr">steps:</span>
  248.    <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v4</span>
  249.    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Create</span> <span class="hljs-string">LFS</span> <span class="hljs-string">file</span> <span class="hljs-string">list</span>
  250.      <span class="hljs-attr">run:</span> <span class="hljs-string">git</span> <span class="hljs-string">lfs</span> <span class="hljs-string">ls-files</span> <span class="hljs-string">-l</span> <span class="hljs-string">|</span> <span class="hljs-string">cut</span> <span class="hljs-string">-d&#x27;</span> <span class="hljs-string">&#x27; -f1 | sort &gt; .lfs-assets-id
  251.    - name: Restore LFS cache
  252.      uses: actions/cache@v3
  253.      id: lfs-cache
  254.      with:
  255.        path: .git/lfs
  256.        key: $<span class="hljs-template-variable">{{ runner.os }}</span>-lfs-$<span class="hljs-template-variable">{{ hashFiles(&#x27;.lfs-assets-id&#x27;) }}</span>-v1
  257.    - name: Git LFS Pull
  258.      run: git lfs pull
  259.  
  260.    - name: Use Node.js
  261.      uses: actions/setup-node@v4
  262.      with:
  263.        node-version: $<span class="hljs-template-variable">{{env.node-version}}</span>
  264.    - run: npm i
  265.    - run: npm run deploy
  266.      env:
  267.        NEOCITIES_API_TOKEN: $<span class="hljs-template-variable">{{ secrets.NEOCITIES_API_TOKEN }}</span>
  268. </span></code></pre>
  269. </li>
  270. </ul>
  271. <p>The <code>async-neocities</code> CLI re-uses the same ENV name as <code>deploy-to-neocities</code> action so migrating to the CLI requires no additional changes to the Actions environment secrets.</p>
  272. <h2 id="clis-vs-actions" tabindex="-1">CLIs vs Actions</h2>
  273. <p>This prompts some questions regarding when are CLIs and when are actions most appropriate. Lets compare the two:</p>
  274. <h3 id="clis" tabindex="-1">CLIs</h3>
  275. <ul>
  276. <li>Pro: Local deploys</li>
  277. <li>Pro: Easily re-usable in CI as well</li>
  278. <li>Con: Requires Node.js, but this is not a problem when already using Node.js</li>
  279. </ul>
  280. <h2 id="actions" tabindex="-1">Actions</h2>
  281. <ul>
  282. <li>Pro: Works great with Non-node ecosystems without requiring a <code>package.json</code> or <code>node_modules</code></li>
  283. <li>Con: Only runs in CI environments</li>
  284. </ul>
  285. <h2 id="conclusion" tabindex="-1">Conclusion</h2>
  286. <p>In addition to the CLI, <code>async-neocities</code> migrates to full Node.js <code>esm</code> and internally enables <code>ts-in-js</code> though the types were far to dynamic to export full type support with the time I had available.</p>
  287. <p>With respect to an implementation plan going forward regarding CLIs vs actions, I’ve summarized my thoughts below:</p>
  288. <p>Implement core functionality as a re-usable library.
  289. Exposing a CLI makes that library an interactive tool that provides a local first workflow and is equally useful in CI.
  290. Exposing the library in an action further opens up the library to a wider language ecosystem which would otherwise ignore the library due to foreign ecosystem ergonomic overhead.
  291. The action is simpler to implement than a CLI but the CLI offers a superior experience within the implemented language ecosystem.</p>
  292. ]]></content>
  293.    <link rel="alternate" href="https://bret.io/blog/2024/async-neocities-bin/"/>
  294.  </entry>
  295.  <entry>
  296.    <id>https://bret.io/blog/2023/reorganized/#2023-12-02T20:49:41.713Z</id>
  297.    <title>Reorganized</title>
  298.    <updated>2023-12-02T20:49:41.713Z</updated>
  299.    <published>2023-12-02T20:49:41.713Z</published>
  300.    <author>
  301.      <name>Bret Comnes</name>
  302.      <uri>https://bret.io</uri>
  303.    </author>
  304.    <content type="html"><![CDATA[<p>Behold, a mildly redesigned and reorganized landing page:</p>
  305. <p><img src="./img/screenshot.png" alt="screenshot of the new website"></p>
  306. <p>It’s still not great, but it should make it easier to keep it up to date going forward.</p>
  307. <p>It has 3 sections:</p>
  308. <ul>
  309. <li><a href="/#">Featured Projects</a>: important projects of note.</li>
  310. <li><a href="/#recent-posts">Recent Posts</a>: now that this site has proper blog support, I can highlight recent posts on the landing page.</li>
  311. <li><a href="/#open-source">Open Source</a>: interesting and notable projects that have found some use and that I still maintain. This section now includes a bunch of open source work from the past year that I’ve never had time to write about.</li>
  312. <li><a href="/#past-projects">Past projects</a>: inactive projects that are not longer active, but still interesting enough to share.</li>
  313. </ul>
  314. <p>I removed a bunch of older inactive projects and links and stashed them in a <a href="/projects/previous-projects/">project</a>.</p>
  315. <p>Additionally, the edit button in the page footer now takes you to the correct page in GitHub for editing, so if you ever see a typo, feel free to send in a fix!</p>
  316. <p>Finally, the <a href="/about/">about</a> page includes a live dump of the dependencies that were used to build the website.</p>
  317. ]]></content>
  318.    <link rel="alternate" href="https://bret.io/blog/2023/reorganized/"/>
  319.  </entry>
  320.  <entry>
  321.    <id>https://bret.io/blog/2023/reintroducing-top-bun/#2023-11-23T15:14:54.910Z</id>
  322.    <title>Reintroducing top-bun 7 🥐</title>
  323.    <updated>2023-11-23T15:14:54.910Z</updated>
  324.    <published>2023-11-23T15:14:54.910Z</published>
  325.    <author>
  326.      <name>Bret Comnes</name>
  327.      <uri>https://bret.io</uri>
  328.    </author>
  329.    <content type="html"><![CDATA[<p>After some unexpected weekends of downtime looking after sick toddlers, I’m happy to re-introduce <a href="https://github.com/bcomnes/top-bun"><code>top-bun</code> v7</a>.</p>
  330. <p>Re-introduce? Well, you may remember <a href="https://github.com/bcomnes/top-bun/tree/v6.0.0"><code>@siteup/cli</code></a>, a spiritual offshoot of <a href="https://github.com/ungoldman/sitedown"><code>sitedown</code></a>, the static site generator that turned a directory of markdown into a website.</p>
  331. <h2 id="tb-v7" tabindex="-1">Whats new with <code>top-bun</code> v7?</h2>
  332. <p>Let’s dive into the new feature, changes and additions in <code>top-bun</code> 7.</p>
  333. <h3 id="rename-to-top-bun" tabindex="-1">Rename to <code>top-bun</code></h3>
  334. <p><code>@siteup/cli</code> is now <code>top-bun</code>.
  335. As noted above, <code>@siteup/cli</code> was a name hack because I didn’t snag the bare <code>npm</code> name when it was available, and someone else had the genius idea of taking the same name. Hey it happens.</p>
  336. <p>I described the project to Chat-GPT and it recommended the following gems:</p>
  337. <ul>
  338. <li><code>quick-brick</code></li>
  339. <li><code>web-erect</code></li>
  340. </ul>
  341. <p>OK Chat-GPT, pretty good, I laughed, but I’m not naming this <code>web-erect</code>.</p>
  342. <p>The kids have a recent obsession with <a href="https://wallaceandgromit.com">Wallace &amp; Gromit</a> and we watched a lot of episodes while she was sick. Also I’ve really been enjoying 🥖 <a href="https://breadcrum.net">bread themes</a> so I decided to name it after Wallace &amp; Gromit’s bakery “Top Bun” in their hit movie <a href="https://en.wikipedia.org/wiki/A_Matter_of_Loaf_and_Death">“A Matter of Loaf and Death”</a>.</p>
  343. <div class="responsive-container"><iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/zXBmZLmfQZ4?si=cGvWktfUU2xnqHrM" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe></div>
  344. <h3 id="a-docs-website" tabindex="-1">A Docs Website</h3>
  345. <p><code>top-bun</code> now builds it’s own repo into a docs website. It’s slightly better than the GitHub README.md view, so go check it out! It even has a real domain name so you know its for real.</p>
  346. <ul>
  347. <li>🌎 <a href="https://top-bun.org">top-bun.org</a></li>
  348. </ul>
  349. <figure>
  350.  <a href="./img/docs-site.png">
  351.    <picture>
  352.      <!-- <source srcset="./static/breadcrum-dark.png" media="(prefers-color-scheme: dark)"> -->
  353.      <img loading="auto" src="./img/docs-site.png" alt="Screenshot of the docs site">
  354.    </picture>
  355.  </a>
  356.  <figcaption>top-bun builds itself into its own docs website in an act dubbed "dogfooding".</figcaption>
  357. </figure>
  358. <h3 id="css-bundling-is-now-handled-by-esbuild" tabindex="-1"><code>css</code> bundling is now handled by <code>esbuild</code></h3>
  359. <p><code>esbuild</code> is an amazing tool. <code>postcss</code> is a useful tool, but its slow and hard to keep up with. In <code>top-bun</code>, <code>css</code> bundling is now handled by <a href="https://esbuild.github.io/content-types/#css"><code>esbuild</code></a>.
  360. <code>css</code> bundling is now faster and less fragile, and still supports many of the same transforms that <code>siteup</code> had before. <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting/Using_CSS_nesting">CSS nesting</a> is now supported in every modern browser so we don’t even need a transform for that. Some basic transforms and prefixes are auto-applied by setting a relatively modern browser target.</p>
  361. <p><code>esbuild</code> doesn’t support import chunking on css yet though, so each <code>css</code> entrypoint becomes its own bundle. If <code>esbuild</code> ever gets this optimization, so will <code>top-bun</code>. In the meantime, <code>global.css</code>, <code>style.css</code> and now <code>layout.style.css</code> give you ample room to generally optimize your scoped css loading by hand. It’s simpler and has less moving parts!</p>
  362. <figure>
  363.  <a href="./img/esbuild-css.png">
  364.    <picture>
  365.      <!-- <source srcset="./static/breadcrum-dark.png" media="(prefers-color-scheme: dark)"> -->
  366.      <img loading="auto" src="./img/esbuild-css.png" alt="Screenshot of esbuild css docs">
  367.    </picture>
  368.  </a>
  369.  <figcaption>The esbuild css docs are worth a cruise through.</figcaption>
  370. </figure>
  371. <h3 id="multi-layout-support" tabindex="-1">Multi-layout support</h3>
  372. <p>You can now have more than one layout!
  373. In prior releases, you could only have a single <code>root</code> layout that you customized on a per-page basis with variables.
  374. Now you can have as many layouts as you need.
  375. <strong>They can even nest</strong>.
  376. Check out this example of a nested layout from this website. It’s named <code>article.layout.js</code> and imports the <code>root.layout.js</code>. It wraps the <code>children</code> and then passes the results to <code>root.layout.js</code>.</p>
  377. <pre><code class="hljs language-js"><span class="hljs-comment">// article.layout.js</span>
  378.  
  379. <span class="hljs-keyword">import</span> { html } <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;uhtml-isomorphic&#x27;</span>
  380. <span class="hljs-keyword">import</span> { sep } <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;node:path&#x27;</span>
  381. <span class="hljs-keyword">import</span> { breadcrumb } <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;../components/breadcrumb/index.js&#x27;</span>
  382.  
  383. <span class="hljs-keyword">import</span> defaultRootLayout <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;./root.layout.js&#x27;</span>
  384.  
  385. <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">articleLayout</span> (args) {
  386.  <span class="hljs-keyword">const</span> { children, ...rest } = args
  387.  <span class="hljs-keyword">const</span> vars = args.<span class="hljs-property">vars</span>
  388.  <span class="hljs-keyword">const</span> pathSegments = args.<span class="hljs-property">page</span>.<span class="hljs-property">path</span>.<span class="hljs-title function_">split</span>(sep)
  389.  <span class="hljs-keyword">const</span> wrappedChildren = html`<span class="language-xml">
  390.    </span><span class="hljs-subst">${breadcrumb({ pathSegments })}</span><span class="language-xml">
  391.    <span class="hljs-tag">&lt;<span class="hljs-name">article</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;article-layout h-entry&quot;</span> <span class="hljs-attr">itemscope</span> <span class="hljs-attr">itemtype</span>=<span class="hljs-string">&quot;http://schema.org/NewsArticle&quot;</span>&gt;</span>
  392.      <span class="hljs-tag">&lt;<span class="hljs-name">header</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;article-header&quot;</span>&gt;</span>
  393.        <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;p-name article-title&quot;</span> <span class="hljs-attr">itemprop</span>=<span class="hljs-string">&quot;headline&quot;</span>&gt;</span></span><span class="hljs-subst">${vars.title}</span><span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
  394.        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;metadata&quot;</span>&gt;</span>
  395.          <span class="hljs-tag">&lt;<span class="hljs-name">address</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;author-info&quot;</span> <span class="hljs-attr">itemprop</span>=<span class="hljs-string">&quot;author&quot;</span> <span class="hljs-attr">itemscope</span> <span class="hljs-attr">itemtype</span>=<span class="hljs-string">&quot;http://schema.org/Person&quot;</span>&gt;</span>
  396.            </span><span class="hljs-subst">${vars.authorImgUrl
  397.              ? html`<span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">height</span>=<span class="hljs-string">&quot;40&quot;</span> <span class="hljs-attr">width</span>=<span class="hljs-string">&quot;40&quot;</span>  <span class="hljs-attr">src</span>=<span class="hljs-string">&quot;</span></span></span><span class="hljs-subst">${vars.authorImgUrl}</span><span class="language-xml"><span class="hljs-tag"><span class="hljs-string">&quot;</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">&quot;</span></span></span><span class="hljs-subst">${vars.authorImgAlt}</span><span class="language-xml"><span class="hljs-tag"><span class="hljs-string">&quot;</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;u-photo&quot;</span> <span class="hljs-attr">itemprop</span>=<span class="hljs-string">&quot;image&quot;</span>&gt;</span>`</span>
  398.              : <span class="hljs-literal">null</span>
  399.            }</span><span class="language-xml">
  400.            </span><span class="hljs-subst">${vars.authorName &amp;&amp; vars.authorUrl
  401.              ? html`<span class="language-xml">
  402.                  <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">&quot;</span></span></span><span class="hljs-subst">${vars.authorUrl}</span><span class="language-xml"><span class="hljs-tag"><span class="hljs-string">&quot;</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;p-author h-card&quot;</span> <span class="hljs-attr">itemprop</span>=<span class="hljs-string">&quot;url&quot;</span>&gt;</span>
  403.                    <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">itemprop</span>=<span class="hljs-string">&quot;name&quot;</span>&gt;</span></span><span class="hljs-subst">${vars.authorName}</span><span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
  404.                  <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>`</span>
  405.              : <span class="hljs-literal">null</span>
  406.            }</span><span class="language-xml">
  407.          <span class="hljs-tag">&lt;/<span class="hljs-name">address</span>&gt;</span>
  408.          </span><span class="hljs-subst">${vars.publishDate
  409.            ? html`<span class="language-xml">
  410.              <span class="hljs-tag">&lt;<span class="hljs-name">time</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;published-date dt-published&quot;</span> <span class="hljs-attr">itemprop</span>=<span class="hljs-string">&quot;datePublished&quot;</span> <span class="hljs-attr">datetime</span>=<span class="hljs-string">&quot;</span></span></span><span class="hljs-subst">${vars.publishDate}</span><span class="language-xml"><span class="hljs-tag"><span class="hljs-string">&quot;</span>&gt;</span>
  411.                <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">&quot;#&quot;</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;u-url&quot;</span>&gt;</span>
  412.                  </span><span class="hljs-subst">${(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(vars.publishDate)).toLocaleString()}</span><span class="language-xml">
  413.                <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
  414.              <span class="hljs-tag">&lt;/<span class="hljs-name">time</span>&gt;</span>`</span>
  415.            : <span class="hljs-literal">null</span>
  416.          }</span><span class="language-xml">
  417.          </span><span class="hljs-subst">${vars.updatedDate
  418.            ? html`<span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">time</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;dt-updated&quot;</span> <span class="hljs-attr">itemprop</span>=<span class="hljs-string">&quot;dateModified&quot;</span> <span class="hljs-attr">datetime</span>=<span class="hljs-string">&quot;</span></span></span><span class="hljs-subst">${vars.updatedDate}</span><span class="language-xml"><span class="hljs-tag"><span class="hljs-string">&quot;</span>&gt;</span>Updated </span><span class="hljs-subst">${(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(vars.updatedDate)).toLocaleString()}</span><span class="language-xml"><span class="hljs-tag">&lt;/<span class="hljs-name">time</span>&gt;</span>`</span>
  419.            : <span class="hljs-literal">null</span>
  420.          }</span><span class="language-xml">
  421.        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  422.      <span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span>
  423.  
  424.      <span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;e-content&quot;</span> <span class="hljs-attr">itemprop</span>=<span class="hljs-string">&quot;articleBody&quot;</span>&gt;</span>
  425.        </span><span class="hljs-subst">${<span class="hljs-keyword">typeof</span> children === <span class="hljs-string">&#x27;string&#x27;</span>
  426.          ? html([children])
  427.          : children /* Support both uhtml and string children. Optional. */
  428.        }</span><span class="language-xml">
  429.      <span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>
  430.  
  431.      <span class="hljs-comment">&lt;!--
  432.        &lt;footer&gt;
  433.            &lt;p&gt;Footer notes or related info here...&lt;/p&gt;
  434.        &lt;/footer&gt;
  435.      --&gt;</span>
  436.    <span class="hljs-tag">&lt;/<span class="hljs-name">article</span>&gt;</span>
  437.    </span><span class="hljs-subst">${breadcrumb({ pathSegments })}</span><span class="language-xml">
  438.  `</span>
  439.  
  440.  <span class="hljs-keyword">return</span> <span class="hljs-title function_">defaultRootLayout</span>({ <span class="hljs-attr">children</span>: wrappedChildren, ...rest })
  441. }
  442. </code></pre>
  443. <h3 id="layout-styles-and-js-bundles" tabindex="-1">Layout styles and js bundles</h3>
  444. <p>With multi-layout support, it made sense to introduce two more style and js bundle types:</p>
  445. <ul>
  446. <li>Layout styles: styles that load on every page that uses a layout</li>
  447. <li>Layout bundles: js bundles that load on every page that uses a layout</li>
  448. </ul>
  449. <p>Prior the <code>global.css</code> and <code>global.client.js</code> bundles served this need.</p>
  450. <figure>
  451.  <a href="./img/layout-bundles.png">
  452.    <picture>
  453.      <!-- <source srcset="./static/breadcrum-dark.png" media="(prefers-color-scheme: dark)"> -->
  454.      <img loading="auto" src="./img/layout-bundles.png" alt="Screenshot of layout bundles in an editor">
  455.    </picture>
  456.  </a>
  457.  <figcaption>Layouts introduce a new asset scope in the form of layout clients and style.</figcaption>
  458. </figure>
  459. <h3 id="layouts-and-global-assets-live-anywhere" tabindex="-1">Layouts and Global Assets live anywhere</h3>
  460. <p>Layouts, and <code>global.client.js</code>, etc used to have to live at the root of the project <code>src</code> directory. This made it simple to find them when building, and eliminated duplicate singleton errors, but the root of websites is already crowded. It was easy enough to find these things anywhere, so now you can organize these special files in any way you like. I’ve been using:</p>
  461. <ul>
  462. <li><code>layouts</code>: A folder full of layouts</li>
  463. <li><code>globals</code>: A folder full of the globally scoped files</li>
  464. </ul>
  465. <figure>
  466.  <a href="./img/anywhere.png">
  467.    <picture>
  468.      <!-- <source srcset="./static/breadcrum-dark.png" media="(prefers-color-scheme: dark)"> -->
  469.      <img loading="auto" src="./img/anywhere.png" alt="Screenshot of globals and layouts living anywhere in an editor">
  470.    </picture>
  471.  </a>
  472.  <figcaption>You are free to organize globals and layouts wherever you want now. The globals and layouts folders are great choice!</figcaption>
  473. </figure>
  474. <h3 id="template-files" tabindex="-1">Template files</h3>
  475. <p>Given the <code>top-bun</code> variable cascade system, and not all website files are html, it made sense to include a templating system for generating any kind of file from the <code>global.vars.js</code> variable set. This lets you generate random website “sidefiles” from your site variables.</p>
  476. <p>It works great for generating RSS feeds for websites built with <code>top-bun</code>. Here is the template file that generates the RSS feed for this website:</p>
  477. <pre><code class="hljs language-js"><span class="hljs-keyword">import</span> pMap <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;p-map&#x27;</span>
  478. <span class="hljs-keyword">import</span> jsonfeedToAtom <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;jsonfeed-to-atom&#x27;</span>
  479.  
  480. <span class="hljs-comment">/**
  481. * <span class="hljs-doctag">@template</span> <span class="hljs-variable">T</span>
  482. * <span class="hljs-doctag">@typedef</span> {<span class="hljs-type">import(&#x27;@siteup/cli&#x27;).TemplateAsyncIterator&lt;T&gt;</span>} <span class="hljs-variable">TemplateAsyncIterator</span>
  483. */</span>
  484.  
  485. <span class="hljs-comment">/** <span class="hljs-doctag">@type</span> {<span class="hljs-type">TemplateAsyncIterator&lt;{
  486. *  siteName: string,
  487. *  siteDescription: string,
  488. *  siteUrl: string,
  489. *  authorName: string,
  490. *  authorUrl: string,
  491. *  authorImgUrl: string
  492. *  layout: string,
  493. *  publishDate: string
  494. *  title: string
  495. * </span>}&gt;} */</span>
  496. <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> * feedsTemplate ({
  497.  <span class="hljs-attr">vars</span>: {
  498.    siteName,
  499.    siteDescription,
  500.    siteUrl,
  501.    authorName,
  502.    authorUrl,
  503.    authorImgUrl
  504.  },
  505.  pages
  506. }) {
  507.  <span class="hljs-keyword">const</span> blogPosts = pages
  508.    .<span class="hljs-title function_">filter</span>(<span class="hljs-function"><span class="hljs-params">page</span> =&gt;</span> [<span class="hljs-string">&#x27;article&#x27;</span>, <span class="hljs-string">&#x27;book-review&#x27;</span>].<span class="hljs-title function_">includes</span>(page.<span class="hljs-property">vars</span>.<span class="hljs-property">layout</span>) &amp;&amp; page.<span class="hljs-property">vars</span>.<span class="hljs-property">published</span> !== <span class="hljs-literal">false</span>)
  509.    .<span class="hljs-title function_">sort</span>(<span class="hljs-function">(<span class="hljs-params">a, b</span>) =&gt;</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>(b.<span class="hljs-property">vars</span>.<span class="hljs-property">publishDate</span>) - <span class="hljs-keyword">new</span> <span class="hljs-title class_">Date</span>(a.<span class="hljs-property">vars</span>.<span class="hljs-property">publishDate</span>))
  510.    .<span class="hljs-title function_">slice</span>(<span class="hljs-number">0</span>, <span class="hljs-number">10</span>)
  511.  
  512.  <span class="hljs-keyword">const</span> jsonFeed = {
  513.    <span class="hljs-attr">version</span>: <span class="hljs-string">&#x27;https://jsonfeed.org/version/1&#x27;</span>,
  514.    <span class="hljs-attr">title</span>: siteName,
  515.    <span class="hljs-attr">home_page_url</span>: siteUrl,
  516.    <span class="hljs-attr">feed_url</span>: <span class="hljs-string">`<span class="hljs-subst">${siteUrl}</span>/feed.json`</span>,
  517.    <span class="hljs-attr">description</span>: siteDescription,
  518.    <span class="hljs-attr">author</span>: {
  519.      <span class="hljs-attr">name</span>: authorName,
  520.      <span class="hljs-attr">url</span>: authorUrl,
  521.      <span class="hljs-attr">avatar</span>: authorImgUrl
  522.    },
  523.    <span class="hljs-attr">items</span>: <span class="hljs-keyword">await</span> <span class="hljs-title function_">pMap</span>(blogPosts, <span class="hljs-keyword">async</span> (page) =&gt; {
  524.      <span class="hljs-keyword">return</span> {
  525.        <span class="hljs-attr">date_published</span>: page.<span class="hljs-property">vars</span>.<span class="hljs-property">publishDate</span>,
  526.        <span class="hljs-attr">title</span>: page.<span class="hljs-property">vars</span>.<span class="hljs-property">title</span>,
  527.        <span class="hljs-attr">url</span>: <span class="hljs-string">`<span class="hljs-subst">${siteUrl}</span>/<span class="hljs-subst">${page.pageInfo.path}</span>/`</span>,
  528.        <span class="hljs-attr">id</span>: <span class="hljs-string">`<span class="hljs-subst">${siteUrl}</span>/<span class="hljs-subst">${page.pageInfo.path}</span>/#<span class="hljs-subst">${page.vars.publishDate}</span>`</span>,
  529.        <span class="hljs-attr">content_html</span>: <span class="hljs-keyword">await</span> page.<span class="hljs-title function_">renderInnerPage</span>({ pages })
  530.      }
  531.    }, { <span class="hljs-attr">concurrency</span>: <span class="hljs-number">4</span> })
  532.  }
  533.  
  534.  <span class="hljs-keyword">yield</span> {
  535.    <span class="hljs-attr">content</span>: <span class="hljs-title class_">JSON</span>.<span class="hljs-title function_">stringify</span>(jsonFeed, <span class="hljs-literal">null</span>, <span class="hljs-string">&#x27;  &#x27;</span>),
  536.    <span class="hljs-attr">outputName</span>: <span class="hljs-string">&#x27;feed.json&#x27;</span>
  537.  }
  538.  
  539.  <span class="hljs-keyword">const</span> atom = <span class="hljs-title function_">jsonfeedToAtom</span>(jsonFeed)
  540.  
  541.  <span class="hljs-keyword">yield</span> {
  542.    <span class="hljs-attr">content</span>: atom,
  543.    <span class="hljs-attr">outputName</span>: <span class="hljs-string">&#x27;feed.xml&#x27;</span>
  544.  }
  545.  
  546.  <span class="hljs-keyword">yield</span> {
  547.    <span class="hljs-attr">content</span>: atom,
  548.    <span class="hljs-attr">outputName</span>: <span class="hljs-string">&#x27;atom.xml&#x27;</span>
  549.  }
  550. }
  551. </code></pre>
  552. <h3 id="page-introspection" tabindex="-1">Page Introspection</h3>
  553. <p>Pages, Layouts and Templates can now introspect every other page in the <code>top-bun</code> build.</p>
  554. <p>You can now easily implement any of the following:</p>
  555. <ul>
  556. <li>RSS feeds</li>
  557. <li>Auto-populating index feeds</li>
  558. <li>Tag systems</li>
  559. </ul>
  560. <p>Pages, Layouts and Templates receive a <code>pages</code> array that includes <a href="https://github.com/bcomnes/top-bun/blob/e83ea560f86dabcda68b0f701c8bfa770aba91ed/lib/build-pages/page-data.js#L25">PageData</a> instances for every page in the build. Variables are already pre-resolved, so you can easily filter, sort and target various pages in the build.</p>
  561. <h3 id="full-type-support" tabindex="-1">Full Type Support</h3>
  562. <p><code>top-bun</code> now has full type support. It’s achieved with <a href="https://github.com/voxpelli/types-in-js"><code>types-in-js</code></a> and it took a ton of time and effort.</p>
  563. <p>The results are nice, but I’m not sure the juice was worth the squeeze. <code>top-bun</code> was working really well before types. Adding types required solidifying a lot of trivial details to make the type-checker happy. I don’t even think a single runtime bug was solved. It did help clarify some of the more complex types that had developed over the first 2 years of development though.</p>
  564. <p>The biggest improvement provided here is that the following types are now exported from <code>top-bun</code>:</p>
  565. <pre><code class="hljs language-ts"><span class="hljs-title class_">LayoutFunction</span>&lt;T&gt;
  566. <span class="hljs-title class_">PostVarsFunction</span>&lt;T&gt;
  567. <span class="hljs-title class_">PageFunction</span>&lt;T&gt;
  568. <span class="hljs-title class_">TemplateFunction</span>&lt;T&gt;
  569. <span class="hljs-title class_">TemplateAsyncIterator</span>&lt;T&gt;
  570. </code></pre>
  571. <p>You can use these to get some helpful auto-complete in LSP supported editors.</p>
  572. <h4 id="types-in-js-not-typescript" tabindex="-1"><code>types-in-js</code> not Typescript</h4>
  573. <p>This was the first major dive I did into a project with <a href="https://github.com/voxpelli/types-in-js"><code>types-in-js</code></a> support.
  574. My overall conclusions are:</p>
  575. <ul>
  576. <li><code>types-in-js</code> provides a superior development experience to developing in <code>.ts</code> by eliminating the development loop build step.</li>
  577. <li><a href="https://jsdoc.app">JSDoc</a> and <code>types-in-js</code> are in conflict with each other. <code>types-in-js</code> should win, its better than JSDoc in almost every way (but you still use both).</li>
  578. <li>Most of the JSDoc auto-generating documentation ecosystem doesn’t support <code>types-in-js</code>. Find something that consumes the generated types instead of consuming the JSDoc blocs.</li>
  579. <li>There are a number of rough edges around importing types.</li>
  580. <li>The final api documentation features are nice.</li>
  581. <li>The internal types feel good, but were mostly a waste of time being introduced after the fact.</li>
  582. <li>I wish there was a way to strictly introduce types on just the public interfaces and work back, but this is challenging because many details come from deep within the program.</li>
  583. <li>Typescript puts upper limits on the dynamism normally allowed with JS. It’s a superset syntax, that forces a subset of language functionality.</li>
  584. <li><code>@ts-ignore</code> liberally. Take a pass or two to remove some later.</li>
  585. </ul>
  586. <h3 id="handlebars-support-in-md-and-html" tabindex="-1">Handlebars support in <code>md</code> and <code>html</code></h3>
  587. <p>Previously, only <code>js</code> pages had access to the variable cascade inside of the page itself. Now <code>html</code> and <code>md</code> pages can access these variables with <a href="https://handlebarsjs.com">handlebars</a> placeholders.</p>
  588. <pre><code class="hljs language-markdown"><span class="hljs-section">## My markdown page</span>
  589.  
  590. Hey this is a markdown page for {{ vars.siteName }} that uses handlebars templates.
  591. </code></pre>
  592. <h3 id="locally-shipped-default-styles" tabindex="-1">Locally shipped default styles</h3>
  593. <p>Previously, if you opted for the default layout, it would import <a href="http://mine-css.neocities.org">mine.css</a> from <a href="https://unpkg.com">unpkg</a>. This worked, but went against the design goal of making <code>top-bun</code> sites as reliable as possible (shipping all final assets to the dest folder).</p>
  594. <p>Now when you build with the default layout, the default stylesheet (and theme picker js code) is built out into your <code>dest</code> folder.</p>
  595. <h3 id="built-in-browser-sync" tabindex="-1">Built-in <code>browser-sync</code></h3>
  596. <p><code>@siteup/cli</code> previously didn’t ship with a development server, meaning you had to run one in parallel when developing. This step is now eliminated now that <code>top-bun</code> ships <a href="https://browsersync.io"><code>browser-sync</code></a>. <code>browser-sync</code> is one of the best Node.js development servers out there and offers a bunch of really helpful dev tools built right in, including scroll position sync so testing across devices is actually enjoyable.</p>
  597. <p>If you aren’t familiar with browser-sync, here are some screenshots of fun feature:</p>
  598. <figure class="borderless">
  599.  <a href="./img/bs-remote-debugging.png">
  600.    <picture>
  601.      <!-- <source srcset="./static/breadcrum-dark.png" media="(prefers-color-scheme: dark)"> -->
  602.      <img loading="auto" src="./img/bs-remote-debugging.png" alt="Screenshot of Browser Sync debugging">
  603.    </picture>
  604.  </a>
  605.  <figcaption>BrowserSync remote debugging</figcaption>
  606. </figure>
  607. <figure class="borderless">
  608.  <a href="./img/bs-grid.png">
  609.    <picture>
  610.      <!-- <source srcset="./static/breadcrum-dark.png" media="(prefers-color-scheme: dark)"> -->
  611.      <img loading="auto" src="./img/bs-grid.png" alt="Screenshot of Browser Sync grid">
  612.    </picture>
  613.  </a>
  614.  <figcaption>BrowserSync Grid overlay</figcaption>
  615. </figure>
  616. <figure class="borderless">
  617.  <a href="./img/bs-outline.png">
  618.    <picture>
  619.      <!-- <source srcset="./static/breadcrum-dark.png" media="(prefers-color-scheme: dark)"> -->
  620.      <img loading="auto" src="./img/bs-outline.png" alt="Screenshot of Browser Sync css outline">
  621.    </picture>
  622.  </a>
  623.  <figcaption>BrowserSync CSS outline</figcaption>
  624. </figure>
  625. <figure class="borderless">
  626.  <a href="./img/bs-depth.png">
  627.    <picture>
  628.      <!-- <source srcset="./static/breadcrum-dark.png" media="(prefers-color-scheme: dark)"> -->
  629.      <img loading="auto" src="./img/bs-depth.png" alt="Screenshot of Browser Sync css depth">
  630.    </picture>
  631.  </a>
  632.  <figcaption>BrowserSync CSS depth</figcaption>
  633. </figure>
  634. <figure class="borderless">
  635.  <a href="./img/bs-depth.png">
  636.    <picture>
  637.      <!-- <source srcset="./static/breadcrum-dark.png" media="(prefers-color-scheme: dark)"> -->
  638.      <img loading="auto" src="./img/bs-depth.png" alt="Screenshot of Browser Sync css depth">
  639.    </picture>
  640.  </a>
  641.  <figcaption>BrowserSync CSS depth</figcaption>
  642. </figure>
  643. <figure class="borderless">
  644.  <a href="./img/bs-network-throttle.png">
  645.    <picture>
  646.      <!-- <source srcset="./static/breadcrum-dark.png" media="(prefers-color-scheme: dark)"> -->
  647.      <img loading="auto" src="./img/bs-network-throttle.png" alt="Screenshot of Browser Sync network throttle">
  648.    </picture>
  649.  </a>
  650.  <figcaption>BrowserSync Network throttle</figcaption>
  651. </figure>
  652. <h3 id="top-bun-eject" tabindex="-1"><code>top-bun</code> eject</h3>
  653. <p><code>top-bun</code> now includes an <code>--eject</code> flag, that will write out the default layout, style, client and dependencies into your <code>src</code> folder and update <code>package.json</code>. This lets you easily get started with customizing default layouts and styles when you decide you need more control.</p>
  654. <pre><code class="hljs language-console">√ default-layout % npx top-bun --eject
  655.  
  656. top-bun eject actions:
  657.  - Write src/layouts/root.layout.mjs
  658.  - Write src/globals/global.css
  659.  - Write src/globals/global.client.mjs
  660.  - Add mine.css@^9.0.1 to package.json
  661.  - Add uhtml-isomorphic@^2.0.0 to package.json
  662.  - Add highlight.js@^11.9.0 to package.json
  663.  
  664. Continue? (Y/n) y
  665. Done ejecting files!
  666. </code></pre>
  667. <p>The default layout is always supported, and its of course safe to rely on that.</p>
  668. <h3 id="improved-log-output-%F0%9F%AA%B5" tabindex="-1">Improved log output 🪵</h3>
  669. <p>The logging has been improved quite a bit. Here is an example log output from building this blog:</p>
  670. <pre><code class="hljs language-console"><span class="hljs-meta prompt_">&gt; </span><span class="language-bash">top-bun --watch</span>
  671.  
  672. Initial JS, CSS and Page Build Complete
  673. bret.io/src =&gt; bret.io/public
  674. ├─┬ projects
  675. │ ├─┬ websockets
  676. │ │ └── README.md: projects/websockets/index.html
  677. │ ├─┬ tron-legacy-2021
  678. │ │ └── README.md: projects/tron-legacy-2021/index.html
  679. │ ├─┬ package-automation
  680. │ │ └── README.md: projects/package-automation/index.html
  681. │ └── page.js: projects/index.html
  682. ├─┬ jobs
  683. │ ├─┬ netlify
  684. │ │ └── README.md: jobs/netlify/index.html
  685. │ ├─┬ littlstar
  686. │ │ └── README.md: jobs/littlstar/index.html
  687. │ ├── page.js:      jobs/index.html
  688. │ ├── zhealth.md:   jobs/zhealth.html
  689. │ ├── psu.md:       jobs/psu.html
  690. │ └── landrover.md: jobs/landrover.html
  691. ├─┬ cv
  692. │ ├── README.md: cv/index.html
  693. │ └── style.css: cv/style-IDZIRKYR.css
  694. ├─┬ blog
  695. │ ├─┬ 2023
  696. │ │ ├─┬ reintroducing-top-bun
  697. │ │ │ ├── README.md: blog/2023/reintroducing-top-bun/index.html
  698. │ │ │ └── style.css: blog/2023/reintroducing-top-bun/style-E2RTO5OB.css
  699. │ │ ├─┬ hello-world-again
  700. │ │ │ └── README.md: blog/2023/hello-world-again/index.html
  701. │ │ └── page.js: blog/2023/index.html
  702. │ ├── page.js:   blog/index.html
  703. │ └── style.css: blog/style-NDOJ4YGB.css
  704. ├─┬ layouts
  705. │ ├── root.layout.js:             root
  706. │ ├── blog-index.layout.js:       blog-index
  707. │ ├── blog-index.layout.css:      layouts/blog-index.layout-PSZNH2YW.css
  708. │ ├── blog-auto-index.layout.js:  blog-auto-index
  709. │ ├── blog-auto-index.layout.css: layouts/blog-auto-index.layout-2BVSCYSS.css
  710. │ ├── article.layout.js:          article
  711. │ └── article.layout.css:         layouts/article.layout-MI62V7ZK.css
  712. ├── globalStyle:               globals/global-OO6KZ4MS.css
  713. ├── globalClient:              globals/global.client-HTTIO47Y.js
  714. ├── globalVars:                global.vars.js
  715. ├── README.md:                 index.html
  716. ├── style.css:                 style-E5WP7SNI.css
  717. ├── booklist.md:               booklist.html
  718. ├── about.md:                  about.html
  719. ├── manifest.json.template.js: manifest.json
  720. ├── feeds.template.js:         feed.json
  721. ├── feeds.template.js-1:       feed.xml
  722. └── feeds.template.js-2:       atom.xml
  723.  
  724. [Browsersync] Access URLs:
  725. --------------------------------------
  726.       Local: http://localhost:3000
  727.    External: http://192.168.0.187:3000
  728. --------------------------------------
  729.          UI: http://localhost:3001
  730. UI External: http://localhost:3001
  731. --------------------------------------
  732. [Browsersync] Serving files from: /Users/bret/Developer/bret.io/public
  733. Copy watcher ready
  734. </code></pre>
  735. <h3 id="support-for-mjs-and-cjs-file-extensions" tabindex="-1">Support for <code>mjs</code> and <code>cjs</code> file extensions</h3>
  736. <p>You can now name your page, template, vars, and layout files with the <code>mjs</code> or <code>cjs</code> file extensions. Sometimes this is a necessary evil. In general, set your <code>type</code> in your <code>package.json</code> correctly and stick with <code>.js</code>.</p>
  737. <h2 id="what%E2%80%99s-next-for-top-bun" tabindex="-1">What’s next for <code>top-bun</code></h2>
  738. <p>The current plan is to keep sitting on this feature set for a while. But I have some ideas:</p>
  739. <ul>
  740. <li>Tell the world about it?</li>
  741. <li>Server routes with <a href="https://fastify.dev">fastify</a> (or expose a plugin to slot into an existing server)?</li>
  742. <li>Deeper integration with <a href="https://ghub.io/uhtml"><code>uhtml</code></a>?</li>
  743. <li>More web-component examples? <code>top-bun</code> is already one of the best environments for implementing sites that use web-components. Page bundles are a perfect place to register components!</li>
  744. <li>Comparisons with other tools in the “enterprise-js” ecosystem?</li>
  745. </ul>
  746. <p>If you try out <code>top-bun</code>, I would love to hear about your experience. Do you like it? Do you hate it? <a href="https://github.com/bcomnes/top-bun/discussions">Open an discussion item.</a> or reach out privately.</p>
  747. <h2 id="history" tabindex="-1">History of <code>top-bun</code></h2>
  748. <p>OK, now time for the story behind <code>top-bun</code> aka <code>@siteup/cli</code>.</p>
  749. <p>I ran some experiments with orthogonal tool composition a few years ago. I realized I could build sophisticated module based websites by composing various tools together by simply running them in parallel.</p>
  750. <p>What does this idea look like? See this snippet of a <code>package.json</code>:</p>
  751. <pre><code class="hljs language-json"> <span class="hljs-punctuation">{</span> <span class="hljs-attr">&quot;scripts&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
  752.    <span class="hljs-attr">&quot;build&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;npm run clean &amp;&amp; run-p build:*&quot;</span><span class="hljs-punctuation">,</span>
  753.    <span class="hljs-attr">&quot;build:css&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;postcss src/index.css -o public/bundle.css&quot;</span><span class="hljs-punctuation">,</span>
  754.    <span class="hljs-attr">&quot;build:md&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;sitedown src -b public -l src/layout.html&quot;</span><span class="hljs-punctuation">,</span>
  755.    <span class="hljs-attr">&quot;build:feed&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;generate-feed src/log --dest public &amp;&amp; cp public/feed.xml public/atom.xml&quot;</span><span class="hljs-punctuation">,</span>
  756.    <span class="hljs-attr">&quot;build:static&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;cpx &#x27;src/**/*.{png,svg,jpg,jpeg,pdf,mp4,mp3,js,json,gif}&#x27; public&quot;</span><span class="hljs-punctuation">,</span>
  757.    <span class="hljs-attr">&quot;build:icon&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;gravatar-favicons --config favicon-config.js&quot;</span><span class="hljs-punctuation">,</span>
  758.    <span class="hljs-attr">&quot;watch&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;npm run clean &amp;&amp; run-p watch:* build:static&quot;</span><span class="hljs-punctuation">,</span>
  759.    <span class="hljs-attr">&quot;watch:css&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;run-s &#x27;build:css -- --watch&#x27;&quot;</span><span class="hljs-punctuation">,</span>
  760.    <span class="hljs-attr">&quot;watch:serve&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;browser-sync start --server &#x27;public&#x27; --files &#x27;public&#x27;&quot;</span><span class="hljs-punctuation">,</span>
  761.    <span class="hljs-attr">&quot;watch:md&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;npm run build:md -- -w&quot;</span><span class="hljs-punctuation">,</span>
  762.    <span class="hljs-attr">&quot;watch:feed&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;run-s build:feed&quot;</span><span class="hljs-punctuation">,</span>
  763.    <span class="hljs-attr">&quot;watch:static&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;npm run build:static -- --watch&quot;</span><span class="hljs-punctuation">,</span>
  764.    <span class="hljs-attr">&quot;watch:icon&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;run-s build:icon&quot;</span><span class="hljs-punctuation">,</span>
  765.    <span class="hljs-attr">&quot;clean&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;rimraf public &amp;&amp; mkdirp public&quot;</span><span class="hljs-punctuation">,</span>
  766.    <span class="hljs-attr">&quot;start&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;npm run watch&quot;</span>
  767.  <span class="hljs-punctuation">}</span>
  768. <span class="hljs-punctuation">}</span>
  769. </code></pre>
  770. <ul>
  771. <li>I have <a href="https://postcss.org"><code>postcss</code></a> building css bundles, enabling an <code>@import</code> based workflow for css, as well as providing various transforms I found useful.</li>
  772. <li>The markdown is built with <a href="https://github.com/ungoldman/sitedown"><code>sitedown</code></a>.</li>
  773. <li>I wrote a tool to generate a RSS feed from markdown called <a href="https://github.com/bcomnes/generate-feed"><code>generate-feed</code></a></li>
  774. <li>I generate favicons from a gravatar identifier with <a href="https://github.com/bcomnes/gravatar-favicons"><code>gravatar-favicons</code></a>.</li>
  775. <li><code>js</code> bundling could easily be added in here with <a href="https://esbuild.github.io"><code>esbuild</code></a> or <a href="https://ghub.io/rollup">rollup</a>.</li>
  776. <li>Steps are grouped into <code>build</code> and <code>watch</code> prefixes, and managed with <a href="https://github.com/bcomnes/npm-run-all2">npm-run-all2</a> which provides shortcuts to running these tasks in parallel.</li>
  777. </ul>
  778. <p>I successfully implemented this pattern across 4-5 different websites I manged. It work beautifully. Some of the things I liked about it:</p>
  779. <ul>
  780. <li>✅ Tools could be swapped out easily.</li>
  781. <li>✅ I could experiment individual ideas on individual sites, without forcing them on every other project.</li>
  782. <li>✅ All of the tools were versioned gated, and I could update them as I had time.</li>
  783. <li>✅ It worked very well with <a href="/projects/package-automation/">release automation</a>.</li>
  784. </ul>
  785. <p>But it had a few drawbacks:</p>
  786. <ul>
  787. <li>❌ I liked the pattern so much, that I wanted to add websites to more projects, but setting this all up was a hassle.</li>
  788. <li>❌ When I discovered a change I liked, re-implementing the change across multiple projects become tedious.</li>
  789. <li>⚠️ Orthogonal tool composition helps keep this arrangement clean and fast, but some steps really do benefit from the awareness of the results of other steps.</li>
  790. </ul>
  791. <p>So I decided to roll up all of the common patterns into a single tool that included the discoveries of this process.</p>
  792. <h2 id="%40siteup%2Fcli-extended-sitedown" tabindex="-1"><code>@siteup/cli</code> extended <code>sitedown</code></h2>
  793. <p>Because it was clear <code>sitedown</code> provided the core structure of this pattern (making the website part), I extended the idea in the project <code>@siteup/cli</code>. Here are some of the initial features that project shipped with:</p>
  794. <ul>
  795. <li>Variable cascade + frontmatter: You could define global and page variables used to customize parts of the site layout. This lets you set the title, or other parts of the <code>&lt;head&gt;</code> tag easily from the <a href="https://jekyllrb.com/docs/front-matter/">frontmatter</a> section of markdown pages.</li>
  796. <li>A <code>js</code> layout: This let you write a simple JS program that receives the variable cascade and child content of the page as a function argument, and return the contents of the page after applying any kind of template tool available in JS.</li>
  797. <li><code>html</code> pages: <a href="https://commonmark.org">CommonMark</a> supports <code>html</code> in markdown, but it also has some funny rules that makes it more picky about how the <code>html</code> is used. I wanted a way to access the full power of <code>html</code> without the overhead of markdown, and this page type unlocked that.</li>
  798. <li><code>js</code> pages: Since I enjoy writing JavaScript, I also wanted a way to support generating pages using any templating system and data source imaginable so I also added support to generate pages from the return value of a JS function.</li>
  799. <li>JavaScript bundling: I wanted to make it super easy to include client side JavaScript, so it included some conventions for setting that up quickly and easily.</li>
  800. <li>CSS bundling: Instead of adding the complexity of css-modules or other transform-based scoped css solution, <code>siteup</code> opted to make it super easy to add a global <code>css</code> bundle, and page scoped <code>css</code> bundles, which both supported a <code>postcss</code> <code>@import</code> workflow and provided a few common transforms to make working with <code>css</code> tolerable (nesting, prefixing etc).</li>
  801. <li>Static files: Including static files was such a common pattern that I also included the ability, to copy over static files without any additional configuration. I believe <code>sitedown</code> also added this feature subsequently.</li>
  802. <li>Asset co-location: I wanted a way to co-locate assets, code and anything near where it was depended upon. It is such a natural way to organize complex collections, and so many tools break this, or require special locations for special files. Let me put it wherever I want in <code>src</code>!</li>
  803. </ul>
  804. <p>After sitting on the idea of <code>siteup</code> for over a year, by the time I published it to <code>npm</code>, the name was taken, so I used the npm org name hack to get a similar name <code>@siteup/cli</code>. <strong>SILLY NPM!</strong></p>
  805. <p>I enjoyed how <code>@siteup/cli</code> came out, and have been using it for 2 year now. Thank you of course to <a href="https://ungoldman.com">ungoldman</a> for laying the foundation of most of these tools and patterns. Onward and upward to <code>top-bun</code>!</p>
  806. <p><img src="./img/contribs.png" alt="contribution graph"></p>
  807. ]]></content>
  808.    <link rel="alternate" href="https://bret.io/blog/2023/reintroducing-top-bun/"/>
  809.  </entry>
  810.  <entry>
  811.    <id>https://bret.io/blog/2023/hello-world-again/#2023-08-30T18:06:24.000Z</id>
  812.    <title>Hello world (again) 🌎</title>
  813.    <updated>2023-08-30T18:06:24.000Z</updated>
  814.    <published>2023-08-30T18:06:24.000Z</published>
  815.    <author>
  816.      <name>Bret Comnes</name>
  817.      <uri>https://bret.io</uri>
  818.    </author>
  819.    <content type="html"><![CDATA[<p><a href="./sunset.jpeg"><img src="./sunset.jpeg" alt="sunset"></a></p>
  820. <p>This blog has been super quiet lately, sorry about that.
  821. I had some grand plans for finishing up my website tools to more seamlessly
  822. support blogging.</p>
  823. <p>The other day around 3AM, I woke up and realized that the tools aren’t stopping me
  824. from writing, I am.
  825. Also my silently implemented policy about not writing about ‘future plans and ideas before they are ready’ was implemented far to strictly.
  826. It is in fact a good thing to write about in-progress ideas and projects slightly out into the future.
  827. This is realistic, interesting, and avoids the juvenile trap of spilling ideas in front of the world only to see you never realize them.
  828. So here I am writing a blog post again.</p>
  829. <p>Anyway, no promises, but it is my goal to write various <em>ahem</em> opinions, thoughts and ideas more often because nothing ever happens unless you write it down.</p>
  830. <h2 id="some-basic-updates" tabindex="-1">Some basic updates</h2>
  831. <p>Here are some updates from the last couple years that didn’t make it onto this site before.</p>
  832. <ul>
  833. <li>I’m back in <a href="https://www.openstreetmap.org/#map=13/40.8455/-124.0532">California</a>.</li>
  834. <li>I moved a bunch (homes, jobs). I bought a home and then sold it. I don’t recommend it!</li>
  835. <li>I have a son and a daughter and am married. I recommend this!</li>
  836. <li>I started <a href="https://hifiwi.fi">HifiWi.fi</a></li>
  837. <li>The first product is <a href="https://breadcrum.net/">Breadcrum</a>, a bookmarking service with textual and media archiving super powers.</li>
  838. <li>HifiWi has subsumed operation of <a href="https://gumcast.com/">Gumcast</a>, a tool to allow podcasting with GumRoad. Without advertising, it has picked up 100s of users organically from search results.</li>
  839. <li>I had a near 2 year tenure at <a href="https://socket.dev">Socket Inc</a> dabbling in GitHub apps and npm security concerns.</li>
  840. <li>I’m now working at <a href="https://socketsupply.co">socketsupply.co</a> on a runtime and P2P full time again. Really happy to be back in this space again.</li>
  841. <li>I still work remote and intend to continue working remote.</li>
  842. <li>A bunch of my projects are running on my own website/app builder too called <a href="https://github.com/bcomnes/top-bun">top-bun</a>. It’s maybe a 3rd of the way done, but has been useful for about 90% of my needs. I would like to write up a proper blog post about it someday.</li>
  843. <li><a href="https://github.com/bcomnes/deploy-to-neocities/">bcomnes/deploy-to-neocities</a> has 300+ public users deploying websites from GitHub actions to Neocities.</li>
  844. <li>My fork <a href="https://github.com/bcomnes/npm-run-all2">bcomnes/npm-run-all2</a> (co-maintained by <a href="https://voxpelli.com">voxpelli.com</a>) has picked up 1000+ public dependents.</li>
  845. <li>Now that twitter (<em>ahem</em> x) has completely abandoned its charter as an open web website, you can find me posting on <a href="https://fosstodon.org/@bcomnes">@bcomnes@fosstodon.org</a> and <a href="https://bsky.app/profile/bret.io">@bret.io on bsky</a>. I would like to make this website authoritative though for me posts though.</li>
  846. <li>I’m very lucky to have a new office that is a 1 minute walk from home:</li>
  847. </ul>
  848. <p><a href="./office.jpeg"><img src="./office.jpeg" alt="pic of the office"></a></p>
  849. ]]></content>
  850.    <link rel="alternate" href="https://bret.io/blog/2023/hello-world-again/"/>
  851.  </entry>
  852.  <entry>
  853.    <id>https://bret.io/projects/tron-legacy-2021/#2021-08-05T23:09:46.781Z</id>
  854.    <title>Tron Legacy 2021</title>
  855.    <updated>2021-08-05T23:09:46.781Z</updated>
  856.    <published>2021-08-05T23:09:46.781Z</published>
  857.    <author>
  858.      <name>Bret Comnes</name>
  859.      <uri>https://bret.io</uri>
  860.    </author>
  861.    <content type="html"><![CDATA[<p>I updated the Sublime Text <a href="https://packagecontrol.io/packages/Tron%20Color%20Scheme"><code>Tron Color Scheme</code></a> today after a few weeks of reworking it for the recent <a href="https://www.sublimetext.com/blog/articles/sublime-text-4">release of Sublime Text 4</a>.</p>
  862. <p>The <a href="https://github.com/bcomnes/sublime-tron-color-scheme/releases/tag/v2.0.0"><code>2.0.0</code> release</a> converts the older <a href="https://www.sublimetext.com/docs/color_schemes_tmtheme.html"><code>.tmTheme</code></a> format into the Sublime specific theme format.
  863. Overall the new Sublime theme format (<a href="https://www.sublimetext.com/docs/color_schemes.html"><code>.sublime-color-scheme</code></a>) is a big improvement, largely due to its simple JSON structure and its variables support.</p>
  864. <p>JSON is, despite the common arguments against it, super readable and easily read and written by humans.
  865. The variable support makes the process of making a theme a whole lot more automatic, since you no longer have to find and replace colors all over the place.</p>
  866. <p>The biggest problem I ran into was poor in-line color highlighting when working with colors, so I ended up using a VSCode plugin called <a href="https://marketplace.visualstudio.com/items?itemName=naumovs.color-highlight"><code>Color Highlight</code></a> in a separate window.
  867. Sublime has a great plugin also called <a href="https://packagecontrol.io/packages/Color%20Highlight"><code>Color Highlight</code></a> that usually works well, but not in this case.
  868. The Sublime <code>Color Highlight</code> variant actually does temporary modifications to color schemes, which seriously gets in the way when working on color scheme files.</p>
  869. <p>The rewrite is based it off of the new <a href="https://www.youtube.com/watch?v=_HoltQwvF2o">Mariana Theme</a> that ships with <abbr title="Sublime Text 4">ST4</abbr>, so the theme should have support for most of the latest features in <abbr title="Sublime Text 4">ST4</abbr> though there are surely features that even Mariana missed.
  870. Let me know if you know of any.</p>
  871. <p>Here a few other points of consideration made during the rewrite:</p>
  872. <ul>
  873. <li>Remove the other theme variants. A breaking change, but they were poorly maintained to begin with.</li>
  874. <li>Renamed the theme file to <code>Tron Legacy 4 (Dark)</code>.</li>
  875. <li><a href="https://github.com/bcomnes/sublime-tron-color-scheme/pull/8">A light theme is in order</a>, but its a non-trivial rewrite to support a light mode.</li>
  876. </ul>
  877. <figure class="borderless">
  878.  <img src="./js.png" alt="Tron Legacy 4 JS Syntax Example">
  879.  <figcaption>JS Syntax example</figcaption>
  880. </figure>
  881. <figure class="borderless">
  882.  <img src="./md.png" alt="Tron Legacy 4 Markdown Syntax Example">
  883.  <figcaption>Markdown Syntax example</figcaption>
  884. </figure>
  885. <figure class="borderless">
  886.  <img src="./py.png" alt="Tron Legacy 4 Python Syntax Example">
  887.  <figcaption>Python Syntax example</figcaption>
  888. </figure>
  889. <figure class="borderless">
  890.  <img src="./c.png" alt="Tron Legacy 4 C Syntax Example">
  891.  <figcaption>C Syntax example</figcaption>
  892. </figure>
  893. <figure class="borderless">
  894.  <img src="./diff.png" alt="Tron Legacy 4 diff Syntax Example">
  895.  <figcaption>diff Syntax example</figcaption>
  896. </figure>
  897. <p>Here a few more relevant links and please let me know what you think if you try it out.</p>
  898. <ul>
  899. <li><a href="https://github.com/bcomnes/sublime-tron-color-scheme">github.com/bcomnes/sublime-tron-color-scheme</a></li>
  900. <li><a href="https://github.com/bcomnes/sublime-tron-color-scheme/releases/tag/v2.0.0">Release page</a></li>
  901. </ul>
  902. <h2 id="syndication" tabindex="-1">Syndication</h2>
  903. <ul>
  904. <li><a href="https://twitter.com/bcomnes/status/1423418998725742602" rel="syndication" class="u-syndication">twitter thread</a></li>
  905. </ul>
  906. <blockquote class="twitter-tweet"><p lang="en" dir="ltr">Sublime Tron Legacy color scheme fully updated for <a href="https://twitter.com/sublimehq?ref_src=twsrc%5Etfw">@sublimehq</a> Text 4. Full syntax support, lots of other small improvements. Also it supports &#39;glow&#39; text✌️ <a href="https://t.co/vShbGThgDF">pic.twitter.com/vShbGThgDF</a></p>&mdash; 🌌🌵🛸Bret🏜👨‍👩‍👧🚙 (@bcomnes) <a href="https://twitter.com/bcomnes/status/1423418998725742602?ref_src=twsrc%5Etfw">August 5, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
  907. <p><small><date>2021-08-05T23:09:46.781Z</date></small></p>
  908. ]]></content>
  909.    <link rel="alternate" href="https://bret.io/projects/tron-legacy-2021/"/>
  910.  </entry>
  911.  <entry>
  912.    <id>https://bret.io/jobs/littlstar/#2021-07-16T19:20:44.161Z</id>
  913.    <title>Littlstar Portfolio</title>
  914.    <updated>2021-07-16T19:20:44.161Z</updated>
  915.    <published>2021-07-16T19:20:44.161Z</published>
  916.    <author>
  917.      <name>Bret Comnes</name>
  918.      <uri>https://bret.io</uri>
  919.    </author>
  920.    <content type="html"><![CDATA[<img src="./littlstar-logo.svg" height=100 width=100 style='float: left; margin-right: 1em;'>
  921. <p>After a short sabbatical in Denmark at <a href="https://archive.ph/m8Igr">Hyperdivision</a>, I joined <a href="https://littlstar.info">Littlstar</a>.
  922. Here is a quick overview of some of the more interesting projects I worked on.
  923. I joined Littlstar during a transitional period of the company and help them transition from a VR video platform to a video on demand and live streaming platform, and now an NFT sales and auction platform.</p>
  924. <h2 id="nycpf-vr-platform" tabindex="-1">NYCPF VR Platform</h2>
  925. <figure class="borderless">
  926.  <img src="./nycpf.png" alt="NYCPF-XR screenshot">
  927.  <figcaption>NYCPF used a local process talking over websocket RPC to a webapp running in a local browser.</figcaption>
  928. </figure>
  929. <p>My first project was picking up on an agency style project developing a novel VR reality training platform for <a href="https://www.nycpolicefoundation.org">NYCPF</a>, powered by a custom <a href="https://github.com/holepunchto/hypercore">hypercore</a> p2p file sharing network, delivering in-house developed unity VR scenarios.
  930. These scenarios could then be brought to various locations like schools or events where NYCPF could guide participants through various law enforcement scenarios with different outcomes based on participant choices within the simulation.</p>
  931. <p>By utilizing a p2p and offline first design approach, we were able to deliver an incredibly flexible and robust delivery platform that had all sorts of difficult features to develop for traditional file distribution platforms such as:</p>
  932. <ul>
  933. <li>Automated offline re-distribution of simulation data to a fleet of devices.</li>
  934. <li>Bittorrent style inverted bandwidth characteristics (more demand = more availability).</li>
  935. <li>Extremely high performance content distribution speeds on commodity hardware.</li>
  936. <li>WAN and LAN peering between devices.</li>
  937. <li>Centrally controlled editorial over available content, with eventual-consistency properties to offline or air-gaped clients.</li>
  938. <li>Cross platform codebase targeting Windows, MacOS and Linux. The dev team utilized all 3 platforms during development, interchangeably.</li>
  939. </ul>
  940. <p>While the project was built on the backs of giants like the <a href="https://hypercore-protocol.org">Hypercore protocol</a>, as well as the amazing work of my <a href="https://github.com/orgs/little-core-labs/people">colleague</a>, I contributed in a number of areas to move the project forward during my time contracting on it.</p>
  941. <ul>
  942. <li>Take ownership of the preexisting React application.</li>
  943. <li>Simplify and maintain a mix of internal open source and closed source packages.</li>
  944. <li>Simplify React app state layer by eliminating the need for Redux in favor of built in React state solutions.</li>
  945. <li>Implement offline mode via Service workers in conjunction with Hypercore.</li>
  946. <li>Packaging and delivery tasks.</li>
  947. <li>Improved progress UI/UX.</li>
  948. <li>Contribute to native packaging tools build with <a href="https://github.com/vercel/pkg">pkg</a> and <a href="https://github.com/little-core-labs/tiny-module-compiler">tiny-module-compiler</a>.</li>
  949. </ul>
  950. <p><img src="./tmc.png" alt="Tiny Module Compiler"></p>
  951. <p>Some of the discrete software packages that resulted from this project are described below.</p>
  952. <h3 id="secure-rpc-protocol" tabindex="-1"><a href="https://github.com/little-core-labs/secure-rpc-protocol"><code>secure-rpc-protocol</code></a></h3>
  953. <p>Secure <a href="https://github.com/little-core-labs/rpc-protocol">rpc-protocol</a> over any duplex socket using <a href="https://github.com/emilbayes/noise-peer">noise-protocol</a>.
  954. This was a refactor of an existing RPC over websocket solution the project was using.
  955. It improved upon the previous secure RPC already used by switching to using the <a href="http://noiseprotocol.org">noise protocol</a> which implements well understood handshake patterns that can be shared and audited between projects, rather than relying on a novel implementation at the RPC layer.
  956. It also decoupled the RPC protocol from the underlying socket being used, so that the RPC system could be used over any other channels we might want in the future.</p>
  957. <figure class="borderless">
  958.  <img src="./secure-rpc-protocol.png" alt="Secure RPC Protocol">
  959.  <figcaption><a href="https://github.com/little-core-labs/secure-rpc-protocol">Secure RPC protocol</a> uses the noise protocol for encryption, and works over any duplex socket.</figcaption>
  960. </figure>
  961. <h3 id="async-folder-walker" tabindex="-1"><a href="https://github.com/bcomnes/async-folder-walker"><code>async-folder-walker</code></a></h3>
  962. <p>An async generator that walks files.
  963. This project was a refactor of an existing project called <a href="https://ghub.io/folder-walker">folder-walker</a> implementing a high performance folder walk algorithm using a more modern async generator API.</p>
  964. <figure>
  965.  <img src="./afw.jpeg" alt="Async folder walker">
  966.  <figcaption><a href="https://github.com/bcomnes/async-folder-walker">Async folder walker</a> provides a modern api to folder and filer walking of a directory.</figcaption>
  967. </figure>
  968. <h3 id="unpacker-with-progress" tabindex="-1"><a href="https://github.com/little-core-labs/unpacker-with-progress"><code>unpacker-with-progress</code></a></h3>
  969. <p><code>unpacker-with-progress</code> is a specialized package that unpacks archives, and provides a progress api in order to provide UI feedback.
  970. One of the deficiencies with the NYCPF project when I started was lack of UI feedback during the extraction process.</p>
  971. <p>VR files are very large, and are delivered compressed to the clients.
  972. After the download is complete, the next step to processing the data is unpacking the content.
  973. This step did not provide any sort of progress feedback to the user because the underlying unpacking libraries did not expose this information, or only exposed some of the information needed to display a progress bar.</p>
  974. <p>This library implemented support for unpacking the various archive formats the project required, and also added an API providing uniform unpacking progress info that could be used in the UI during unpacking tasks.</p>
  975. <figure class="borderless">
  976.  <img src="./uwp.png" alt="Unpacker with progress">
  977.  <figcaption><a href="https://github.com/little-core-labs/unpacker-with-progress">unpacker-with-progress</a> is a consistency layer on top of a few unpacking libraries, with an emphasis on progress reporting for use in UI applications.</figcaption>
  978. </figure>
  979. <h3 id="hchacha20" tabindex="-1"><a href="https://github.com/little-core-labs/hchacha20"><code>hchacha20</code></a></h3>
  980. <p>One of the more interesting side projects I worked on was porting over some of the <a href="https://doc.libsodium.org">libsodium</a> primitives to <a href="https://github.com/sodium-friends/sodium-javascript">sodium-javascript</a>.
  981. I utilized a technique I learned about at Hyperdivision where one can write web assembly by hand in the <a href="https://developer.mozilla.org/en-US/docs/WebAssembly/Understanding_the_text_format">WAT format</a>, providing a much wider set of data types, providing the type guarantees needed to write effective crypto.</p>
  982. <p>While the WAT was written for HChaCha20, the effort was quite laborious and it kicked off a debate as to whether it would be better to just wrap <a href="https://github.com/jedisct1/libsodium.js">libsodium-js</a> (the official libsodium js port) in a wrapper that provided the <a href="https://github.com/sodium-friends/sodium-universal">sodium-universal</a> API.  This was achieved by another group in <a href="https://github.com/geut/sodium-javascript-plus">geut/sodium-javascript-plus</a> which successfully ran hypercores in the browser using that wrapper.</p>
  983. <p>Ultimately, this effort was scrapped, determining that noise peer connections in the browser are redundant to webRTC encryption and https sockets.
  984. It was a fun and interesting project none the less.</p>
  985. <figure class="borderless">
  986.  <img src="./hchacha.png" alt="Some HChacha WAT Code">
  987.  <figcaption>A peek into the world of WASM via WAT.</figcaption>
  988. </figure>
  989. <h3 id="reconnecting-sockets" tabindex="-1">Reconnecting sockets</h3>
  990. <p>We were having some state transition bugs between the webapp and the local server process, where the app could get into strange indeterminate states.
  991. Both had independent reconnect logic wrapped up with application code, and it added a lot of chaos to understanding how each process was behaving when things went wrong (especially around sleep cycles on machines).</p>
  992. <p>I implemented a generic reconnecting state machine that could accept any type of socket, and we were able to reduce the number of state transition bugs we had.</p>
  993. <ul>
  994. <li><a href="https://github.com/little-core-labs/reconnecting-socket"><code>little-core-labs/reconnecting-socket</code></a></li>
  995. <li><a href="https://github.com/little-core-labs/reconnecting-simple-websocket"><code>little-core-labs/reconnecting-simple-websocket</code></a></li>
  996. </ul>
  997. <figure class="borderless">
  998.  <img src="./rsw.png" alt="Reconnecting simple websocket">
  999. </figure>
  1000. <h2 id="little-core-labs" tabindex="-1">Little Core Labs</h2>
  1001. <img src="./lcl.png" width=100 height=100 style='float: left; margin-right: 1em;'>
  1002. <p>After working on various agency projects at Littlstar, we formed a separate organization to start a fresh rewrite of the technology stack.</p>
  1003. <p>My high level contributions:</p>
  1004. <ul>
  1005. <li>Terraform running in Github actions</li>
  1006. <li>Provisioning AWS infrastructure.</li>
  1007. <li>Helping come up with how all of this stuff will work.</li>
  1008. </ul>
  1009. <p>This was an amazing founders-style opportunity to help rethink and re-implement years of work that had developed at Littlstar prior to  joining.
  1010. Effectively starting from 0, we rethought the entire technology pipeline, from operations, to infrastructure, to deployment, resulting in something really nice, modern, minimal, low maintenance and malleable.</p>
  1011. <ul>
  1012. <li><a href="https://github.com/little-core-labs">Little Core Labs Github</a></li>
  1013. <li><a href="https://twitter.com/littlecorelabs">Little Core Labs Twitter</a></li>
  1014. </ul>
  1015. <figure>
  1016.  <img src="./overview.jpg" alt="A snapshot of our cloudcraft">
  1017.  <figcaption>
  1018.    A screencap of our cloudformation diagram.
  1019.  </figcaption>
  1020. </figure>
  1021. <h2 id="terraform-ops" tabindex="-1">Terraform ops</h2>
  1022. <p>A culmination of ingesting the <a href="https://amzn.to/3er6lBB">“Terraform: Up &amp; Running”</a> and <a href="https://amzn.to/3rbNakx">“AWS Certified Solutions Architect”</a> books, as well as building off existing organizational experience with AWS, I helped research and design an operations plan using <a href="https://www.terraform.io">Terraform</a> and <a href="https://github.com/features/actions">GitHub Actions</a>.</p>
  1023. <div style='display: grid; grid-template-columns: 1fr 1fr; justify-items: center;'>
  1024.  <div>
  1025.    <img widht='199.5' height='250' src='./aws.jpg'/>
  1026.  </div>
  1027.  <div>
  1028.    <img width='190.5' height='250' src='./terraform.jpg'/>
  1029.  </div>
  1030. </div>
  1031. <p>This arrangement has proven powerful and flexible.
  1032. While it isn’t perfect, it has been effective and reliable and cheap, despite its imperfections in relation to some of the more esoteric edge cases of Terraform.</p>
  1033. <p>A quick overview of how its arrange:</p>
  1034. <ul>
  1035. <li>We have a global terraform repository with segmented terraform files for various services.</li>
  1036. <li>The <code>ops</code> global repo runs terraform in a bootstrapped GitHub actions environment.</li>
  1037. <li>We can create service level repos from the <code>ops</code> terraform repo, that in turn contain their own Terraform files specific to that service.</li>
  1038. <li>One of the benefits was that the GitHub environment worked along side a local environment, due to the use of AWS secrets manager and GitHub actions secrets (all managed in Terraform), so debugging was easy and flexible.</li>
  1039. </ul>
  1040. <h2 id="github-actions" tabindex="-1">Github actions</h2>
  1041. <img width='140' height='140' src='./actions.svg' alt='Actions logo' style='float: left; margin-right: 1em;' />
  1042. <p>One of the drawbacks of rolling our own Terraform CI infrastructure was that we had to tackle many small edge cases inside the GitHub actions environment.</p>
  1043. <p>It was nice to learn about the various types of custom GitHub actions one can write, as well as expand that knowlege to the rest of the org, but it also ate up a number of days focusing on DevOps problems specific to our CI environment.</p>
  1044. <p>Here are some of the problems I helped solve in the actions environment.</p>
  1045. <ul>
  1046. <li><a href="https://github.com/little-core-labs/install-terraform">install-terraform</a> - Install terraform at a specific version.</li>
  1047. <li><a href="https://github.com/little-core-labs/netrc-creds">netrc-creds</a> - Set up <a href="https://www.gnu.org/software/inetutils/manual/html_node/The-_002enetrc-file.html">netrc</a> credentials.</li>
  1048. <li><a href="https://github.com/little-core-labs/get-git-tag">get-git-tag</a> - Easily get a git tag.</li>
  1049. </ul>
  1050. <p><img src="./netrc.png" alt="netrc-creds"></p>
  1051. <h2 id="sdk-js" tabindex="-1">sdk-js</h2>
  1052. <p>I helped lay the framework for the initial version of <code>sdk-js</code>, the Little Core Labs unified library used to talk to the various back-end services at Little Core Labs.</p>
  1053. <p>One of underlying design goals was to solve for the <a href="https://nodejs.org/api/esm.html">newly introduced native ESM features in node</a>, in such a way that the package could be consumed directly in the browser, natively as ESM in node, but also work in dual CJS/ESM environments like Next.js.
  1054. While this did add some extra overhead to the project, it serves as a design pattern we can pull from in the future, as well as a provide a highly compatible but modern API client.</p>
  1055. <p>I also extracted out this dual package pattern into a reusable template.</p>
  1056. <ul>
  1057. <li><a href="https://github.com/bcomnes/esm-template">bcomnes/esm-template</a></li>
  1058. </ul>
  1059. <p><img src="./sdk.png" alt="sdk-js"></p>
  1060. <h2 id="rad.live" tabindex="-1"><a href="https://rad.live">rad.live</a></h2>
  1061. <p><img src="./rad.jpg" alt="Rad.live"></p>
  1062. <p>I was the principal engineer on the new <a href="https://rad.live">Rad.live</a> website.
  1063. I established and implemented the tech stack, aiming to take a relatively conservative take on a code base that would maximize the readability and clarity for a Dev team that would eventually grow in size.</p>
  1064. <p>A high level, the application is simply an app written with:</p>
  1065. <ul>
  1066. <li><a href="https://nextjs.org">Next.js</a> - React with a framework to provide standard bundling, SSR and routing features.</li>
  1067. <li><a href="https://swr.vercel.app">SWR</a> - Declarative data <a href="https://reactjs.org/docs/hooks-intro.html">‘hooks’</a>, minimizing/eliminating complex state management throughout the app.</li>
  1068. <li><a href="https://github.com/little-core-labs/gqlr">GQLR</a> - A simple GraphQL client tuned to ‘just work’.</li>
  1069. <li><a href="https://github.com/vercel/styled-jsx">styled-jsx</a> - A simple and understandable CSS-In-JS framework that minimizes orthogonal layout CSS.</li>
  1070. </ul>
  1071. <p>From a development perspective, it was important to have testing and automation from the beginning.
  1072. For this we used:</p>
  1073. <ul>
  1074. <li>GitHub actions to run CI on every interaction.</li>
  1075. <li>Organizational standard linting that supported the unique needs of React.</li>
  1076. <li><a href="https://storybook.js.org">Storybook</a> for data/state independent component testing (Using the new and improved storybook API)</li>
  1077. <li>Automated component testing, with a minimum of ‘does it render’ testing utilizing the storybook stories.</li>
  1078. <li>Release automation.</li>
  1079. </ul>
  1080. <p>Overall, the codebase has had two other seasoned developers (one familiar and one new at React) jump in and find it productive based on individual testimony.
  1081. Gathering feedback from those thatI work with on technical decisions that I make is an important feedback look I always try to incorporate when green fielding projects.
  1082. Additionally, it has been a relatively malleable code base that is easy to add MVP features to and is in a great position to grow.</p>
  1083. <h3 id="vision.css" tabindex="-1"><code>vision.css</code></h3>
  1084. <p><img src="./vision.jpg" alt="vision.css"></p>
  1085. <p>I implemented a custom design system in tandem with our design team.
  1086. This project has worked out well, and has so far avoided ‘css lockout’, where only one developer can effectively make dramatic changes to an app layout due to an undefined and overly general orthogonal ‘global style sheet’.</p>
  1087. <p>The way this was achieved was by focusing on a simple global CSS style sheet that implements the base HTML elements in accordance with the design system created by the design team.
  1088. While this does result in a few element variants that are based on a global style class name, they remain in the theme of only styling ‘built in’ html elements, so there is little question what might be found in the global style sheet, and what needs to be a scoped css style.</p>
  1089. <p>Some of the features we used for vision.css</p>
  1090. <ul>
  1091. <li>Standard CSS syntax with a selection of a few ‘yet-to-be-released’ css syntax features.</li>
  1092. <li>A <a href="https://postcss.org">postcss</a> build pipeline.</li>
  1093. <li>Heavy use of <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties">CSS variables</a>.</li>
  1094. <li>A <a href="https://little-core-labs.github.io/vision.css/">website</a> that contains a style-guide and preview of the various styled elements.</li>
  1095. </ul>
  1096. <h3 id="eslint-config-12core" tabindex="-1"><a href="https://github.com/little-core-labs/eslint-config-12core"><code>eslint-config-12core</code></a></h3>
  1097. <p><img src="./eslint.png" alt="eslint-config-12core"></p>
  1098. <p>Linting is the “spell check” of code, but its hard to agree on what rules to follow.
  1099. Like most things, having a standard set of rules that is good-enough is always better than no rules, and usually better than an unpredictable and unmaintainable collection of project-unique rules.</p>
  1100. <p>I put together a shared ESLint config that was flexible enough for most projects so far at Little Core Labs based on the ‘StandardJS’ ruleset, but remained flexible enough to modify unique org requirements.
  1101. Additionally, I’ve implemented it across many of the projects in the Github Org.</p>
  1102. <ul>
  1103. <li><a href="https://github.com/little-core-labs/eslint-config-12core"><code>eslint-config-12core</code></a></li>
  1104. </ul>
  1105. <h3 id="gqlr" tabindex="-1"><a href="https://github.com/little-core-labs/gqlr"><code>gqlr</code></a></h3>
  1106. <p><img src="./gqlr.png" alt="gqlr"></p>
  1107. <p><code>gqlr</code> is a simplified fork of <a href="https://github.com/prisma-labs/graphql-request">graphql-request</a>.</p>
  1108. <p>This relatively simple wrapper around the JS fetch API has a gaggle of upstream maintainers with various needs that don’t really match our needs.</p>
  1109. <p>The fork simplified and reduced code redundancy, improved maintainability through automation, fixed bugs and weird edge cases and dramatically improved errors and error handling at the correct level of the tech stack.
  1110. These changes would have unlikely been accepted upstream, so by forking we are able to gain the value out of open source resources, while still being able to finely tune them for our needs, as well as offer those changes back to the world.</p>
  1111. <ul>
  1112. <li><a href="https://github.com/little-core-labs/gqlr"><code>gqlr</code></a></li>
  1113. </ul>
  1114. <h3 id="local-storage-proxy" tabindex="-1"><a href="https://github.com/bcomnes/local-storage-proxy"><code>local-storage-proxy</code></a></h3>
  1115. <p><img src="./lsp.gif" alt=""></p>
  1116. <p>A configuration solution that allows for persistent overrides stored in <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage">local storage</a>, including cache busting capabilities.  Implemented with a recursive <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy">JS proxy</a> to simulate native object interactions over a window.localstorage interface.</p>
  1117. <ul>
  1118. <li><a href="https://github.com/bcomnes/local-storage-proxy"><code>local-storage-proxy</code></a></li>
  1119. </ul>
  1120. <h3 id="community-maintenance" tabindex="-1">Community maintenance</h3>
  1121. <p>We ended up taking on maintainence of a few other packages, providing fixes and improvements where the original authors seem to have left off.</p>
  1122. <ul>
  1123. <li><a href="https://github.com/little-core-labs/date-input-polyfill">little-core-labs/date-input-polyfill</a> - Polyfill for HTML5 date input element.</li>
  1124. <li><a href="https://github.com/little-core-labs/chromafi">little-core-labs/chromafi</a></li>
  1125. </ul>
  1126. <h3 id="video-platform" tabindex="-1">Video platform</h3>
  1127. <p>Here are some snapshots of the video platform we launched.</p>
  1128. <p><img src="./channels.jpg" alt=""></p>
  1129. <p><img src="./player.jpg" alt=""></p>
  1130. <h3 id="nft-auction-platform" tabindex="-1">NFT Auction Platform</h3>
  1131. <p>Here are some screenshots of the NFT auction platform I helped build.
  1132. The UI was fully responsive and updated on the fly to new results, thanks to the powers of SWR.</p>
  1133. <p><img src="./auction.jpg" alt=""></p>
  1134. <h3 id="marketing-pages" tabindex="-1">Marketing pages</h3>
  1135. <p>I did a few marketing pages as well.</p>
  1136. <p><img src="./marketing-rad.jpg" alt=""></p>
  1137. <p><img src="./marketing-nft.jpg" alt=""></p>
  1138. <p><img src="./marketing-onnit.jpg" alt=""></p>
  1139. <p><img src="./marketing-ps.jpg" alt=""></p>
  1140. <h2 id="conclusion" tabindex="-1">Conclusion</h2>
  1141. <p>While this isn’t everything I did at Littlstar, it captures many of the projects I enjoyed working on, and can hopefully provide some insights into my skills, interests and experiences from the past year.</p>
  1142. ]]></content>
  1143.    <link rel="alternate" href="https://bret.io/jobs/littlstar/"/>
  1144.  </entry>
  1145.  <entry>
  1146.    <id>https://bret.io/projects/package-automation/#2020-09-29T17:50:58.562Z</id>
  1147.    <title>Fully Automated Luxury Space Age Package Maintenance</title>
  1148.    <updated>2020-09-29T17:50:58.562Z</updated>
  1149.    <published>2020-09-29T17:50:58.562Z</published>
  1150.    <author>
  1151.      <name>Bret Comnes</name>
  1152.      <uri>https://bret.io</uri>
  1153.    </author>
  1154.    <content type="html"><![CDATA[<p><small>tldr;</small> The full package maintenance life cycle should be automated and can be broken down into the following levels of automation sophistication:</p>
  1155. <ul>
  1156. <li><a href="#level-0"><abbr title="git and Github">Level 0</abbr></a>: <code>git</code> + Github</li>
  1157. <li><a href="#level-1"><abbr title="Automated Tests + CI">Level 1</abbr></a>: Automated tests, running in <abbr title="Continuous Integration">CI</abbr></li>
  1158. <li><a href="#level-2"><abbr title="Dependency Bots">Level 2</abbr></a>: Use a dependency bot to keep on top of maintenance chores</li>
  1159. <li><a href="#level-3"><abbr title="Automated Changelog and Release Consistency Scripts">Level 3</abbr></a>: Automated changelog and release consistency scripts</li>
  1160. <li><a href="#level-4"><abbr title="Publishing Bots">Level 4</abbr></a>: Human triggered, bot-run version cutting and publishing</li>
  1161. <li><a href="#level-5"><abbr title="Project Generation">Level 5</abbr></a>: Capture common automations into a boiler plate generator</li>
  1162. </ul>
  1163. <p><mark>These solutions focus on Node.js + npm packages, automated on Github Actions, but the underlying principals are general to any language or automation platform.</mark></p>
  1164. <h2 id="background" tabindex="-1">Background</h2>
  1165. <p>Maintaining lots of software packages is burdensome.
  1166. Scaling open source package maintenance beyond a single contributor who understands the release life cycle is challenging.
  1167. Long <code>CONTRIBUTING.md</code> files are often the goto solution, but are easily overlooked.</p>
  1168. <figure>
  1169.  <img src="./shrek-swamp.jpg" alt="Shrek swamp">
  1170.  <figcaption>A pre-automation package is a lot like the shrek swamp.  Fixing things requires a slog through the mud, and its constantly at risk for overgrowth.  (<a href="https://shrek.fandom.com/wiki/Swamp">Img Source</a>)</figcaption>
  1171. </figure>
  1172. <p>In the end, automating the package life cycle so that it can maintain itself, is the only way to realistically scale a large set of packages in a maintainable way.</p>
  1173. <figure>
  1174.  <img src="./forerunner_structure.jpg" alt="Forerunne Archetecture">
  1175.  <figcaption>A fully automated luxury space age package maintains itself, re-activating after years of abandonment to operate the same as it did the day of its creation. This is about as much value I can get out of this silly analogy. (<a href="https://lortarkam.wordpress.com/2017/04/12/how-should-the-forerunners-really-look/">Img Source</a>)</figcaption>
  1176. </figure>
  1177. <p>For a long time I didn’t seek out automation solutions for package maintenance beyond a few simple solutions like testing and <abbr title="Continuous Integration">CI</abbr>.
  1178. Instead I had a lengthly ritual that looked approximately like this:</p>
  1179. <pre><code class="hljs language-console"><span class="hljs-meta prompt_"># </span><span class="language-bash">🔮</span>
  1180. git checkout -b my-cool-branch
  1181. <span class="hljs-meta prompt_"># </span><span class="language-bash"><span class="hljs-keyword">do</span> some work</span>
  1182. <span class="hljs-meta prompt_"># </span><span class="language-bash">update tests</span>
  1183. <span class="hljs-meta prompt_"># </span><span class="language-bash">update docs</span>
  1184. npm run test
  1185. git commit -am &#x27;Describe the changes&#x27;
  1186. git push -u
  1187. hub browse
  1188. <span class="hljs-meta prompt_"># </span><span class="language-bash"><span class="hljs-keyword">do</span> the PR process</span>
  1189. <span class="hljs-meta prompt_"># </span><span class="language-bash">merge the PR</span>
  1190. git checkout master
  1191. git pull
  1192. git branch --delete my-cool-branch
  1193. <span class="hljs-meta prompt_"># </span><span class="language-bash">hand edit changelog</span>
  1194. git add CHANGELOG.md
  1195. git commit -m &#x27;CHANGELOG&#x27;
  1196. npm version {major,minor,patch}
  1197. git push &amp;&amp; git push --follow-tags
  1198. npx gh-release
  1199. npm publish
  1200. <span class="hljs-meta prompt_"># </span><span class="language-bash">😅</span>
  1201. </code></pre>
  1202. <p>It was ritual, a muscle memory.</p>
  1203. <p>Over the years, I’ve managed to automate away large amount of raw labor to various bots, tools and platforms that tend to build on one another and are often usable in isolation or adopted one at a time.
  1204. I’ve broken various tools and opportunities for automation into levels, with each level building on the complexity contained in the level below.</p>
  1205. <h2 id="level-0%3A-git-and-github" tabindex="-1"><span id="level-0"><abbr title="git and Github">Level 0</abbr></span>: <code>git</code> and Github</h2>
  1206. <p>You are already automating your packages to a large extent by your use of <a href="https://git-scm.com"><code>git</code></a>.
  1207. <code>git</code> automates the process of working on code across multiple computers and collaborating on it with other people, and <a href="https://github.com">Github</a> is the central platform to coordinate and distribute that code.</p>
  1208. <figure>
  1209.  <img src="./git.gif" alt="The old git header">
  1210.  <figcaption>The old git mascot knew what was up.</figcaption>
  1211. </figure>
  1212. <p>If you are new to programming or learning <code>git</code>, its helpful to understand you are learning a tool used to automate the process by which you can cooperatively work on code with other people and bots.</p>
  1213. <figure class="borderless">
  1214.  <picture>
  1215.    <source srcset="./fork-dark.png" media="(prefers-color-scheme: dark)">
  1216.    <img src="./fork-light.png" alt="Screenshot of Fork.app">
  1217.  </picture>
  1218.  <figcaption>Use the <a href="https://git-fork.com">tree view</a> Luke!</figcaption>
  1219. </figure>
  1220. <p>This isn’t an article about <code>git</code> though, so I won’t dive more into that.</p>
  1221. <h2 id="level-1%3A-automated-tests-%2B-ci" tabindex="-1"><span id="level-1"><abbr title="Automated Tests + CI">Level 1</abbr></span>: Automated Tests + <abbr title="Continuous Integration">CI</abbr></h2>
  1222. <p>There is no debate.
  1223. Software isn’t “done” until it has tests.
  1224. The orthodox position is that you shouldn’t be allowed to write code until the tests are written (<a href="https://en.wikipedia.org/wiki/Test-driven_development">TDD</a>).
  1225. No matter the methodology, you are automating a verification process of the package that you would normally have to perform by hand.</p>
  1226. <h3 id="test-runners" tabindex="-1">Test runners</h3>
  1227. <p>These are my preferred test runners for Node.js:</p>
  1228. <ul>
  1229. <li><a href="https://ghub.io/tape">tape</a>
  1230. <ul>
  1231. <li>No “test harness”.</li>
  1232. <li>Simple, reliable.  Just a library, with a CLI that supports <a href="https://ghub.io/glob">globbing</a>.</li>
  1233. <li>It’s done. No surprises.</li>
  1234. </ul>
  1235. </li>
  1236. <li><a href="https://ghub.io/tap">tap</a>
  1237. <ul>
  1238. <li>Similar to tape.</li>
  1239. <li>Test runner defaults to improved default test globbing (e.g. <code>foo.test.js</code>).</li>
  1240. <li>async/await support</li>
  1241. <li>Built in <a href="https://github.com/istanbuljs/nyc">NYC</a> support.</li>
  1242. <li>Bad native ESM support.</li>
  1243. <li>Still room for evolution.</li>
  1244. </ul>
  1245. </li>
  1246. </ul>
  1247. <figure class="borderless">
  1248.  <img src="./tap.png" alt="Screenshot of tap code">
  1249.  <figcaption>tap offers some nice test runner features and better async support.</figcaption>
  1250. </figure>
  1251. <h3 id="additional-tests" tabindex="-1">Additional Tests</h3>
  1252. <p>Unit tests that you run with a test runner are not the only type of test though.
  1253. There are lots of other easy tests you can throw at your package testing step that provide a ton of value:</p>
  1254. <ul>
  1255. <li><a href="https://en.wikipedia.org/wiki/Lint_(software)">Linting</a> (<a href="https://standardjs.com"><code>standard</code></a>, <a href="https://eslint.org"><code>eslint</code></a> etc)
  1256. <ul>
  1257. <li>Enforce code style and catch errors!  Basically spell check on code.  This should really be the very first test on every package, since it helps you write correct code.</li>
  1258. </ul>
  1259. </li>
  1260. <li><a href="https://github.com/dependency-check-team/dependency-check"><code>dependency-check</code></a>
  1261. <ul>
  1262. <li>Verify dependencies listed in <code>package.json</code> matches usage in the actual code.</li>
  1263. <li>This test fails if it finds packages in <code>package.json</code> that are no longer used in code or if it finds pacakges in use in the code that are not listed in <code>package.json</code> which would cause a runtime error.</li>
  1264. </ul>
  1265. </li>
  1266. <li>Running a build
  1267. <ul>
  1268. <li>Simply make <code>npm run build</code> part of your test life cycle.</li>
  1269. <li>If your package has some sort of build step, simply running a build and checking if it runs without error is a very helpful test. It’s also simple to set up.</li>
  1270. </ul>
  1271. </li>
  1272. <li>Code Coverage metrics
  1273. <ul>
  1274. <li><a href="https://github.com/istanbuljs/nyc">nyc</a> is the defacto test coverage tool (built into tap).</li>
  1275. <li><a href="https://github.com/bcoe/c8">c8</a> is a native V8 coverage tool, but is lest robust than nyc.</li>
  1276. <li>These don’t need to be pass/fail in terms of coverage going up or down (but they can be).</li>
  1277. <li>These metrics help you validate the tests you wrote are actually running the parts of the code you expect (or don’t expect).</li>
  1278. <li>Platform integration for ingesting these metrics are available, but usually not worth it.  Simply having console output is sufficient most of the time.
  1279. <ul>
  1280. <li><a href="https://coveralls.io">coveralls.io</a> is a nice indie service that provides coverage metrics reporting.</li>
  1281. </ul>
  1282. </li>
  1283. </ul>
  1284. </li>
  1285. </ul>
  1286. <figure class="borderless">
  1287.  <picture>
  1288.    <source srcset="./coveralls-dark.png" media="(prefers-color-scheme: dark)">
  1289.    <img src="./coveralls-light.png" alt="Screenshot of Coveralls">
  1290.  </picture>
  1291.  <figcaption>Platforms like coveralls provide nice coverage tracking, but the cost of setting up integrations makes them optional.</figcaption>
  1292. </figure>
  1293. <h3 id="managing-complex-test-scripts" tabindex="-1">Managing complex test scripts</h3>
  1294. <p>Running multiple tests under <code>npm test</code> can result in a long, difficult to maintain <code>test</code> script.  Install <a href="https://github.com/bcomnes/npm-run-all2"><code>npm-run-all2</code></a> as a <code>devDependency</code> to break each <code>package.json</code> <code>test</code> command into its own sub-script, and then use the globbing feature to run them all in parallel (<code>run-p</code>) or in series (<code>run-s</code>):</p>
  1295. <pre><code class="hljs language-json"><span class="hljs-punctuation">{</span>
  1296.  <span class="hljs-attr">&quot;scripts&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
  1297.    <span class="hljs-attr">&quot;test&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;run-s test:*&quot;</span><span class="hljs-punctuation">,</span>
  1298.    <span class="hljs-attr">&quot;test:deps&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;dependency-check . --no-dev --no-peer&quot;</span><span class="hljs-punctuation">,</span>
  1299.    <span class="hljs-attr">&quot;test:standard&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;standard&quot;</span><span class="hljs-punctuation">,</span>
  1300.    <span class="hljs-attr">&quot;test:tap&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;tap&quot;</span>
  1301.  <span class="hljs-punctuation">}</span>
  1302. <span class="hljs-punctuation">}</span>
  1303. </code></pre>
  1304. <p>When testing locally, and an individual test is failing, you can bypass the other tests and run just the failing test:</p>
  1305. <pre><code class="hljs language-console"><span class="hljs-meta prompt_"># </span><span class="language-bash">run just the dep tests</span>
  1306. npm run test:deps
  1307. </code></pre>
  1308. <p><a href="https://github.com/bcomnes/npm-run-all2"><code>npm-run-all2</code></a> is a fantastic tool to keep your <a href="https://docs.npmjs.com/misc/scripts"><code>npm run</code></a> scripts manageable.</p>
  1309. <p>This builds on the fantastic and 2014 classic Keith Cirkel blog post <a href="https://www.keithcirkel.co.uk/how-to-use-npm-as-a-build-tool/">How to Use npm as a Build Tool</a>.</p>
  1310. <h3 id="actually-automating-the-tests" tabindex="-1">Actually automating the tests</h3>
  1311. <p>While its obvious that writing automated tests is a form of automation, its still very common to see projects not take the actual step of automating the run step of the tests by hooking them up to a <abbr title="Continuous Integration">CI</abbr> system that runs the tests on every interaction with the code on Github.
  1312. Services like <a href="https://travis-ci.org">TravisCI</a> have been available for <strong>FREE</strong> for years, and there is literally no valid excuse not to have this set up.</p>
  1313. <p>Although <a href="https://travis-ci.org">TravisCI</a> has served many projects well over the years, <a href="https://github.com/features/actions">Github Actions</a> is a newer and platform native solution that many projects are now using.  Despite the confusing name, Github Actions is primarily a <abbr title="Continuous Integration">CI</abbr> service.</p>
  1314. <figure class="borderless">
  1315.  <picture>
  1316.    <source srcset="./actions-dark.png" media="(prefers-color-scheme: dark)">
  1317.    <img src="./actions-light.png" alt="Screenshot of Github actions">
  1318.  </picture>
  1319.  <figcaption>Github actions has its own oddities, but overall its proven to be an extremely flexible and useful CI service, built right into the platform you are using if you are doing any kind of open source.</figcaption>
  1320. </figure>
  1321. <p>Create the following action file in your package repo and push it up to turn on <abbr title="Continuous Integration">CI</abbr>.</p>
  1322. <pre><code class="hljs language-yaml"><span class="hljs-comment"># .github/workflows/tests.yml</span>
  1323. <span class="hljs-attr">name:</span> <span class="hljs-string">tests</span>
  1324.  
  1325. <span class="hljs-attr">on:</span> [<span class="hljs-string">push</span>]
  1326.  
  1327. <span class="hljs-attr">jobs:</span>
  1328.  <span class="hljs-attr">test:</span>
  1329.    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">$</span>
  1330.  
  1331.    <span class="hljs-attr">strategy:</span>
  1332.      <span class="hljs-attr">matrix:</span>
  1333.        <span class="hljs-attr">os:</span> [<span class="hljs-string">ubuntu-latest</span>]
  1334.        <span class="hljs-attr">node:</span> [<span class="hljs-number">14</span>]
  1335.  
  1336.    <span class="hljs-attr">steps:</span>
  1337.    <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v2.3.2</span>
  1338.    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Use</span> <span class="hljs-string">Node.js</span> <span class="hljs-string">$</span>
  1339.      <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-node@v2.1.1</span>
  1340.      <span class="hljs-attr">with:</span>
  1341.        <span class="hljs-attr">node-version:</span> <span class="hljs-string">$</span>
  1342.    <span class="hljs-bullet">-</span> <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">i</span>
  1343.    <span class="hljs-bullet">-</span> <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">test</span>
  1344. </code></pre>
  1345. <p>For more information on Action syntax and directives, see:</p>
  1346. <ul>
  1347. <li><a href="https://docs.github.com/en/actions">Actions Docs</a></li>
  1348. <li><a href="https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions">Actions context reference</a></li>
  1349. <li><a href="https://docs.github.com/en/actions/learn-github-actions/managing-complex-workflows">Actions examples</a></li>
  1350. </ul>
  1351. <h3 id="automated-checks" tabindex="-1">Automated Checks</h3>
  1352. <p>Once you have a test suite set up, running in <abbr title="Continuous Integration">CI</abbr>, any pull request to your package features the results of the “Checks API”.
  1353. Various tests and integrations will post their results on every change to the pull request in the form of “running”, “pass” or “fail”.</p>
  1354. <p>The benefit to the checks status in the pull request UI is, depending on the quality and robustness of your test suite, you can have some amount of confidence that you can safely merge the proposed changes, while still having things work the way you expect, including the newly proposed changes.
  1355. No matter the reliability of the test suite, it is still important to read and review the code.</p>
  1356. <figure class="borderless">
  1357.  <picture>
  1358.    <source srcset="./checks-dark.png" media="(prefers-color-scheme: dark)">
  1359.    <img src="./checks-light.png" alt="Screenshot of Checks API UI">
  1360.  </picture>
  1361. </figure>
  1362. <h2 id="level-2%3A-dependency-bots" tabindex="-1"><span id="level-2"><abbr title="Dependency Bots">Level 2</abbr></span>: Dependency Bots</h2>
  1363. <p>Your package has dependences.  Be it your test runner, or other packages imported or required into your package.  They help provide valuable function with little upfront cost.</p>
  1364. <p>Dependencies form the foundation that your package is built upon.  But that foundation is made of shifting sands⏳.
  1365. Dependencies have their own dependencies, which all have to slowly morph and change with the underlying platform and dependency changes.
  1366. Like a garden, if you don’t tend to the weeds and periodically water it with dependency updates, the plants will die.</p>
  1367. <p>With <code>npm</code>, you normally update dependencies by grabbing the latest copy of the code and <a href="https://docs.npmjs.com/cli-commands/outdated.html">checking for outdated</a> packages:</p>
  1368. <pre><code class="hljs language-console">git checkout master
  1369. git pull
  1370. rm -rf node_modules
  1371. npm i
  1372. npm outdated
  1373. </code></pre>
  1374. <figure class="borderless">
  1375.  <picture>
  1376.    <source srcset="./outdated-dark.png" media="(prefers-color-scheme: dark)">
  1377.    <img src="./outdated-light.png" alt="Screenshot of npm outdated output">
  1378.  </picture>
  1379.  <figcaption><a href="https://docs.npmjs.com/cli-commands/outdated.html"><code>npm outdated</code></a> will give you a list of your dependencies that have fallen behind their semver range and updates that are available outside of their semver range.</figcaption>
  1380. </figure>
  1381. <p>Checking for updates any time you work on the package is not a bad strategy, but it becomes tiresome, and can present a large amount of maintenance work, unrelated to the prompting task at hand, if left to go a long time.  A good package doesn’t need to change much, so it may rarely ever be revisited and rot indefinitely.</p>
  1382. <p><strong>Enter dependency bots.</strong></p>
  1383. <p>A dependency bot monitors your package repositories for dependency updates.
  1384. When a dependency update is found, it automatically creates a <abbr title="Pull Request">PR</abbr> with the new version.
  1385. If you have <abbr title="Automated Tests + CI">Level 1</abbr> automation setup, this <abbr title="Pull Request">PR</abbr> will run your tests with the updated version of the dependency.
  1386. The results will (mostly) inform you if its safe to apply the update, and also give you a button to press to apply the change.
  1387. No typing or console required! 🚽🤳</p>
  1388. <p><abbr title="Automated Tests + CI">Level 1</abbr> automation isn’t required to use a dependency bot, but you won’t have any way to automatically validate the change, so they are much less useful in that case.</p>
  1389. <h3 id="dependabot" tabindex="-1">Dependabot</h3>
  1390. <img height="275" src="./dependabot.svg">
  1391. <p>Github now has a dependency bot built in called <a href="https://docs.github.com/en/github/administering-a-repository/enabling-and-disabling-version-updates">dependabot</a>.
  1392. To turn it on, create the following file in your packages Github repo:</p>
  1393. <pre><code class="hljs language-yaml"><span class="hljs-comment"># .github/dependabot.yml</span>
  1394.  
  1395. <span class="hljs-attr">version:</span> <span class="hljs-number">2</span>
  1396. <span class="hljs-attr">updates:</span>
  1397.  <span class="hljs-comment"># Enable version updates for npm</span>
  1398.  <span class="hljs-bullet">-</span> <span class="hljs-attr">package-ecosystem:</span> <span class="hljs-string">&quot;npm&quot;</span>
  1399.    <span class="hljs-comment"># Look for `package.json` and `lock` files in the `root` directory</span>
  1400.    <span class="hljs-attr">directory:</span> <span class="hljs-string">&quot;/&quot;</span>
  1401.    <span class="hljs-comment"># Check the npm registry for updates every day (weekdays)</span>
  1402.    <span class="hljs-attr">schedule:</span>
  1403.      <span class="hljs-attr">interval:</span> <span class="hljs-string">&quot;daily&quot;</span>
  1404.  <span class="hljs-comment"># Enable updates to github actions</span>
  1405.  <span class="hljs-bullet">-</span> <span class="hljs-attr">package-ecosystem:</span> <span class="hljs-string">&quot;github-actions&quot;</span>
  1406.    <span class="hljs-attr">directory:</span> <span class="hljs-string">&quot;/&quot;</span>
  1407.    <span class="hljs-attr">schedule:</span>
  1408.      <span class="hljs-attr">interval:</span> <span class="hljs-string">&quot;daily&quot;</span>
  1409. </code></pre>
  1410. <p>This enables updates for npm and github actions.  It offers other ecosystems as well.  See the <a href="https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates">dependabot docs</a> for more info.</p>
  1411. <h3 id="in-range-breaking-change-%F0%9F%9A%A8" tabindex="-1">In-range breaking change 🚨</h3>
  1412. <p>Before dependabot, there was a now-shut-down service called <a href="https://greenkeeper.io">Greenkeeper.io</a> which provided a very similar service.  It offered a very interesting feature which I’m still not sure if dependabot has yet.</p>
  1413. <figure class="borderless">
  1414.  <picture>
  1415.    <source srcset="./in-range-dark.png" media="(prefers-color-scheme: dark)">
  1416.    <img src="./in-range-light.png" alt="Screenshot of Greenkeepers in-range breaking change PRs">
  1417.  </picture>
  1418.  <figcaption>Greenkeeper would run continuous checks for every new dependency in an and out of your packages semver range, proactively identifying breaking changes that could sneak in.</figcaption>
  1419. </figure>
  1420. <p>It would run tests every time a dependency in your package was updated, in and out of <abbr title="Semantic Version">semver</abbr> range.</p>
  1421. <p>For in range updates that passed, nothing would happen.
  1422. For in range updates that failed, it would open a <abbr title="Pull Request">PR</abbr> alerting you that one of your dependencies inadvertently released a breaking change as a non-breaking change.
  1423. This was a fantastic feature, and really demonstrated the heights that automated tests, <abbr title="Continuous Integration">CI</abbr>, an ecosystem that fully utilized <abbr title="Semantic Version">semver</abbr> and dependency bots could achieve together.</p>
  1424. <p>Sadly, I haven’t seen other services or languages quite reach these heights of automation sophistication (many ecosystems even lack any kind of <code>major</code> version gate rage), but perhaps as awareness increases of these possibilities more people will demand it.</p>
  1425. <p>There is a lot more room for innovation in this space.  It would be great to get get periodic reports regarding the health of downstream dependency chains (e.g. if you are depending on a project that is slowly rotting and not maintaining its deps).
  1426. As of now, dependabot seems to be recovering from a <a href="https://twitter.com/bcomnes/status/1303421464910192641">post acquisition engineering fallout</a>, but I hope that they can get these kinds of features back into reality sooner than later.</p>
  1427. <h3 id="linter-bots%3F" tabindex="-1">Linter bots?</h3>
  1428. <p>There are bots out there that can send in automated code changes source off lint tests and other code analysis tools.  While this is ‘cool’, these tasks are better served at the testing level.
  1429. Automated code changes for failing lint tests should really just be apart of the human development cycle with whatever IDE or editor they use.
  1430. Still, the bot layer is open to experimentation, so go forth an experiment all you want, though note that external service integrations have a heavy integration cost usually. 🤖</p>
  1431. <h2 id="level-3%3A-automated-changelog-and-release-consistency-scripts" tabindex="-1"><span id="level-3"><abbr title="Automated Changelog and Release Consistency Scripts">Level 3</abbr></span>: Automated Changelog and Release Consistency Scripts</h2>
  1432. <p>Quick recap:</p>
  1433. <ul>
  1434. <li><a href="#level-0"><abbr title="git and Github">Level 0</abbr></a>: we’ve automated collaboration and are receiving patch PRs from humans.</li>
  1435. <li><a href="#level-1"><abbr title="Automated Tests + CI">Level 1</abbr></a>: We automatically validate code with a test suite that runs on every <abbr title="Pull Request">PR</abbr>.</li>
  1436. <li><a href="#level-2"><abbr title="Dependency Bots">Level 2</abbr></a>: We have a bot sending in patches for tedious dependency management tasks.</li>
  1437. </ul>
  1438. <p>That means our package is going to morph and change with time (hopefully not too much though).  We need a way way to communicate that clearly to downstream dependents, be that us, someone else on the team or a large base of <a href="https://www.hanselman.com/blog/DarkMatterDevelopersTheUnseen99.aspx">dark-matter developers</a>.</p>
  1439. <p>The way to do this is with a CHANGELOG.md file.  Or release notes in a Github release page. Or ideally both.  <a href="https://keepachangelog.com/en/1.0.0/">keepachangelog.com</a> offers a good overview on the correct format a CHANGELOG.md should follow.</p>
  1440. <figure class="borderless">
  1441.  <picture>
  1442.    <source srcset="./changelog-dark.png" media="(prefers-color-scheme: dark)">
  1443.    <img src="./changelog-light.png" alt="Screenshot of various changelog specs">
  1444.  </picture>
  1445.  <figcaption><a href="https://keepachangelog.com">keepachangelog.com</a>, <a href="https://www.conventionalcommits.org/en/v1.0.0/">Conventional Commits</a> and <a href="https://semantic-release.gitbook.io/semantic-release/">Semantic Release</a> are all conventions and systems for documenting changes to a project.</figcaption>
  1446. </figure>
  1447. <p>This is a tedious process.  If you work with other people, they might not be as motivated as you to handcraft an artisan CHANGELOG file.  In my experience, the handcrafted, artisan CHANGELOG is too much work and easy to forget about.  Also, I haven’t found a good linter tool to enforce its maintenance.</p>
  1448. <h3 id="auto-changelog" tabindex="-1"><a href="https://ghub.io/auto-changelog"><code>auto-changelog</code></a></h3>
  1449. <p><a href="https://ghub.io/auto-changelog"><code>auto-changelog</code></a> is a tool that takes your git history and generates a CHANGELOG that is almost-just-as-good as the artisan handcrafted one.  Hooking this tool into your package’s <a href="https://docs.npmjs.com/misc/scripts"><code>version</code> life cycle</a> enforces that it is run when a new version is generated with <code>npm version {major,minor,patch}</code>.
  1450. While <a href="https://keepachangelog.com/en/1.0.0/">keepachangelog.com</a> advocates for the handcrafted version, and discourages ‘git commit dumps’, as long as you are halfway concious of your <code>git</code> commit logs (as you should be), the <code>auto-changelog</code> output is generally still useful.
  1451. You can even follow <a href="https://www.conventionalcommits.org/en/v1.0.0/">conventionalcommits.org</a> if you want an even more structured git log.</p>
  1452. <p>Automating <code>auto-changelog</code> to run during <code>npm version</code><sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup> is easy.
  1453. Install it as a <code>devDependency</code> and set up the following script in <code>package.json</code>:</p>
  1454. <pre><code class="hljs language-json"><span class="hljs-punctuation">{</span>
  1455.  <span class="hljs-attr">&quot;scripts&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
  1456.    <span class="hljs-attr">&quot;version&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;auto-changelog -p --template keepachangelog auto-changelog --breaking-pattern &#x27;BREAKING CHANGE:&#x27; &amp;&amp; git add CHANGELOG.md&quot;</span>
  1457.  <span class="hljs-punctuation">}</span>
  1458. <span class="hljs-punctuation">}</span>
  1459. </code></pre>
  1460. <p>The <code>version</code> script is a <code>npm run</code> lifecycle script that runs after the <code>package.json</code> version is bumped, but before the git commit with the change are created. Kind of a mouthful, but with nice results.</p>
  1461. <figure class="borderless">
  1462.  <picture>
  1463.    <source srcset="./changelog-example-dark.png" media="(prefers-color-scheme: dark)">
  1464.    <img src="./changelog-example-light.png" alt="Screenshot of an auto-changelog changelog.">
  1465.  </picture>
  1466.  <figcaption><a href="https://ghub.io/auto-changelog"><code>auto-changelog</code></a> generates satisfactory changelogs.  The consistency it provides exceeds the value a hand written changelog can provide due to its inconsistent nature.</figcaption>
  1467. </figure>
  1468. <h3 id="publishing-consistency-scripts" tabindex="-1">Publishing consistency scripts</h3>
  1469. <p>Ok, so we have local changes merged in, and we’ve created a new version of the module with an automated changelog generated as part of the <code>npm version</code> commit.  Time to get this all pushed out and published!  By hand we could do:</p>
  1470. <pre><code class="hljs language-console">git push --follow-tags
  1471. <span class="hljs-meta prompt_"># </span><span class="language-bash">copy contents of changelog</span>
  1472. <span class="hljs-meta prompt_"># </span><span class="language-bash">create a new Github release on the new tag with the changelog contents</span>
  1473. npm publish
  1474. </code></pre>
  1475. <p>But that is tedious.
  1476. And what happens when your colleague forgets to push the git commit/tag to Github and just publishes to <code>npm</code>?
  1477. Or more likely, they just forget to create the Github release, creating inconsistency in the release process.</p>
  1478. <p>The solution is to automate all of this!
  1479. Use the <a href="https://docs.npmjs.com/misc/scripts"><code>prepublishOnly</code></a> hook to run all of these tasks automatically before publishing to npm via <code>npm publish</code>.
  1480. Incorporate a tool like <a href="https://ghub.io/gh-release"><code>gh-release</code></a> to create a Github release page for the new tag with the contents of your freshly minted <a href="https://ghub.io/auto-changelog"><code>auto-changelog</code></a>.</p>
  1481. <pre><code class="hljs language-json"><span class="hljs-punctuation">{</span>
  1482.  <span class="hljs-attr">&quot;scripts&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
  1483.    <span class="hljs-attr">&quot;prepublishOnly&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;git push --follow-tags &amp;&amp; gh-release -y&quot;</span>
  1484.  <span class="hljs-punctuation">}</span>
  1485. <span class="hljs-punctuation">}</span>
  1486. </code></pre>
  1487. <figure>
  1488.  <video controls width="100%" preload="metadata">
  1489.    <source src="./gh-release-demo.mp4#t=0.5" type="video/mp4">
  1490.  </video>
  1491.  <figcaption><a href="https://ghub.io/gh-release"><code>gh-release</code></a> makes it easy to create Github releases from a CHANGELOG.md.</figcaption>
  1492. </figure>
  1493. <p>The result of this is our release process is returned to the lowest common denominator of process dictated by npm:</p>
  1494. <pre><code class="hljs language-console">npm version {major,minor,patch}
  1495. npm publish
  1496. </code></pre>
  1497. <p>But we still get all of these results, completely automated:</p>
  1498. <ul>
  1499. <li>Automated changelog updating via <a href="https://ghub.io/auto-changelog"><code>auto-changelog</code></a></li>
  1500. <li>Automated version bumping and creating a new version <code>git</code> commit+tag.</li>
  1501. <li>Automated pushing to Github (including new tags)</li>
  1502. <li>Automated Github release creation via <a href="https://ghub.io/gh-release"><code>gh-release</code></a>, with the new contents of CHANGELOG</li>
  1503. <li>Published assets to <code>npm</code>.</li>
  1504. </ul>
  1505. <h3 id="all-together" tabindex="-1">All together</h3>
  1506. <p>Those two run scripts together:</p>
  1507. <pre><code class="hljs language-json"><span class="hljs-punctuation">{</span>
  1508.  <span class="hljs-attr">&quot;scripts&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
  1509.    <span class="hljs-attr">&quot;version&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;auto-changelog -p --template keepachangelog auto-changelog --breaking-pattern &#x27;BREAKING CHANGE:&#x27; &amp;&amp; git add CHANGELOG.md&quot;</span><span class="hljs-punctuation">,</span>
  1510.    <span class="hljs-attr">&quot;prepublishOnly&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;git push --follow-tags &amp;&amp; gh-release -y&quot;</span>
  1511.  <span class="hljs-punctuation">}</span>
  1512. <span class="hljs-punctuation">}</span>
  1513. </code></pre>
  1514. <h4 id="extending-with-a-build-step" tabindex="-1">Extending with a build step</h4>
  1515. <p>Some packages have builds steps.  No problem, these are easily incorporated into the above flow:</p>
  1516. <pre><code class="hljs language-json"><span class="hljs-punctuation">{</span>
  1517.  <span class="hljs-attr">&quot;scripts&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
  1518.    <span class="hljs-attr">&quot;build&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;do some build command here&quot;</span><span class="hljs-punctuation">,</span>
  1519.    <span class="hljs-attr">&quot;prepare&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;npm run build&quot;</span><span class="hljs-punctuation">,</span>
  1520.    <span class="hljs-attr">&quot;version&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;run-s prepare version:*&quot;</span><span class="hljs-punctuation">,</span>
  1521.    <span class="hljs-attr">&quot;version:changelog&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;auto-changelog -p --template keepachangelog auto-changelog --breaking-pattern &#x27;BREAKING CHANGE:&#x27;&quot;</span><span class="hljs-punctuation">,</span>
  1522.    <span class="hljs-attr">&quot;version:git&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;git add CHANGELOG.md dist&quot;</span><span class="hljs-punctuation">,</span>
  1523.    <span class="hljs-attr">&quot;prepublishOnly&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;git push --follow-tags &amp;&amp; gh-release -y&quot;</span>
  1524.  <span class="hljs-punctuation">}</span>
  1525. <span class="hljs-punctuation">}</span>
  1526. </code></pre>
  1527. <p>Since version becomes a bit more complex, we can break it down into pieces with <a href="https://github.com/bcomnes/npm-run-all2"><code>npm-run-all2</code></a> as we did in the testing step.  We ensure we run fresh builds on development install (<code>prepare</code>), and also when we <code>version</code>.  We capture any updated build outputs in <code>git</code> during the version step by staging the <code>dist</code> folder (or whatever else you want to capture in your <code>git</code> version commit).</p>
  1528. <p>This pattern was documented well by <a href="http://twitter.com/swyx">@swyx</a>: <a href="https://dev.to/swyx/semi-automatic-npm-and-github-releases-with-gh-release-and-auto-changelog-4b5a">Semi-Automatic npm and GitHub Releases with <code>gh-release</code> and <code>auto-changelog</code></a>.</p>
  1529. <h2 id="level-4%3A-publishing-bots-%F0%9F%A4%96" tabindex="-1"><span id="level-4"><abbr title="Publishing Bots">Level 4</abbr></span>: Publishing Bots 🤖</h2>
  1530. <p>We now have a process for fully managing the maintenance and release cycle of our package, but we are still left to pull down any changes from our Github repo and run these release commands, as simple as they are now.
  1531. You can’t really do this on your phone (easily) and someone else on the project still might manage to <strong>not</strong> run <code>npm version</code> and just hand-bump the version number for some reason, bypassing all our wonderful automation.</p>
  1532. <p>What would be cool is if we could kick of a special <abbr title="Continuous Integration">CI</abbr> program that would run <code>npm version &amp;&amp; npm publish</code> for us, at the push of a button.</p>
  1533. <p>It turns out Github-Actions has a feature now called <a href="https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows#workflow_dispatch"><code>workflow_dispatch</code></a>, which lets you press a button on the repos actions page on GitHub and trigger a <abbr title="Continuous Integration">CI</abbr> flow with some input.</p>
  1534. <figure class="borderless">
  1535.  <picture>
  1536.    <source srcset="./workflow-dispatch-dark.png" media="(prefers-color-scheme: dark)">
  1537.    <img src="./workflow-dispatch-light.png" alt="Screenshot of workflow dispatch UI">
  1538.  </picture>
  1539.  <figcaption><a href="https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows#workflow_dispatch"><code>workflow_dispatch</code></a> actions lets you trigger an action from your browser, with simple textual inputs.  Use it as a simple shared deployment environment.</figcaption>
  1540. </figure>
  1541. <p>Implementing <a href="https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows#workflow_dispatch"><code>workflow_dispatch</code></a> is easy: create a new action workflow file with the following contents:</p>
  1542. <pre><code class="hljs language-yaml"><span class="hljs-comment"># .github/workflows/release.yml</span>
  1543.  
  1544. <span class="hljs-attr">name:</span> <span class="hljs-string">npm</span> <span class="hljs-string">version</span> <span class="hljs-string">&amp;&amp;</span> <span class="hljs-string">npm</span> <span class="hljs-string">publish</span>
  1545.  
  1546. <span class="hljs-attr">on:</span>
  1547.  <span class="hljs-attr">workflow_dispatch:</span>
  1548.    <span class="hljs-attr">inputs:</span>
  1549.      <span class="hljs-attr">newversion:</span>
  1550.        <span class="hljs-attr">description:</span> <span class="hljs-string">&#x27;npm version {major,minor,patch}&#x27;</span>
  1551.        <span class="hljs-attr">required:</span> <span class="hljs-literal">true</span>
  1552.  
  1553. <span class="hljs-attr">env:</span>
  1554.  <span class="hljs-attr">node_version:</span> <span class="hljs-number">14</span>
  1555.  
  1556. <span class="hljs-attr">jobs:</span>
  1557.  <span class="hljs-attr">version_and_release:</span>
  1558.    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
  1559.    <span class="hljs-attr">steps:</span>
  1560.    <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v2.3.2</span>
  1561.      <span class="hljs-attr">with:</span>
  1562.        <span class="hljs-comment"># fetch full history so things like auto-changelog work properly</span>
  1563.        <span class="hljs-attr">fetch-depth:</span> <span class="hljs-number">0</span>
  1564.    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Use</span> <span class="hljs-string">Node.js</span> <span class="hljs-string">$</span>
  1565.      <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-node@v2.1.1</span>
  1566.      <span class="hljs-attr">with:</span>
  1567.        <span class="hljs-attr">node-version:</span> <span class="hljs-string">$</span>
  1568.        <span class="hljs-comment"># setting a registry enables the NODE_AUTH_TOKEN env variable where we can set an npm token.  REQUIRED</span>
  1569.        <span class="hljs-attr">registry-url:</span> <span class="hljs-string">&#x27;https://registry.npmjs.org&#x27;</span>
  1570.    <span class="hljs-bullet">-</span> <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">i</span>
  1571.    <span class="hljs-bullet">-</span> <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">test</span>
  1572.    <span class="hljs-bullet">-</span> <span class="hljs-attr">run:</span> <span class="hljs-string">git</span> <span class="hljs-string">config</span> <span class="hljs-string">--global</span> <span class="hljs-string">user.email</span> <span class="hljs-string">&quot;user@email.com&quot;</span>
  1573.    <span class="hljs-bullet">-</span> <span class="hljs-attr">run:</span> <span class="hljs-string">git</span> <span class="hljs-string">config</span> <span class="hljs-string">--global</span> <span class="hljs-string">user.name</span> <span class="hljs-string">&quot; $&quot;</span>
  1574.    <span class="hljs-bullet">-</span> <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">version</span> <span class="hljs-string">$</span>
  1575.    <span class="hljs-bullet">-</span> <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">publish</span>
  1576.      <span class="hljs-attr">env:</span>
  1577.        <span class="hljs-attr">GH_RELEASE_GITHUB_API_TOKEN:</span> <span class="hljs-string">$</span>
  1578.        <span class="hljs-attr">NODE_AUTH_TOKEN:</span> <span class="hljs-string">$</span>
  1579. </code></pre>
  1580. <p>Next, <a href="https://docs.npmjs.com/creating-and-viewing-authentication-tokens">generate an npm token</a> with publishing rights.</p>
  1581. <p>Then set that token as a <a href="https://docs.github.com/en/free-pro-team@latest/actions/reference/encrypted-secrets">repo secret</a> called <code>NPM_TOKEN</code>.</p>
  1582. <figure class="borderless">
  1583.  <picture>
  1584.    <source srcset="./secrets-dark.png" media="(prefers-color-scheme: dark)">
  1585.    <img src="./secrets-light.png" alt="Screenshot of Github secrets">
  1586.  </picture>
  1587.  <figcaption><a href="https://docs.github.com/en/free-pro-team@latest/actions/reference/encrypted-secrets"><code>GitHub</code></a> secrets allows you to securely store tokens for use in your GitHub Actions runs.  It's not bulletproof, bit its pretty good.</figcaption>
  1588. </figure>
  1589. <p>Now you can visit the actions tab on the repo, select the <code>npm version &amp;&amp; npm publish</code> action, and press run, passing in either <code>major</code>, <code>minor</code>, or <code>patch</code> as the input, and a GitHub action will kick off running our <abbr title="Automated Changelog and Release Consistency Scripts">Level 3</abbr> version and release automations along with publishing a release to npm and GitHub.</p>
  1590. <p><strong>Note:</strong> Its recommended that you <code>.gitignore</code> <code>package-lock.json</code> files, otherwise they end up in a library source, where it provides little benefit and lots of drawbacks.</p>
  1591. <p>I created a small actions called <a href="https://github.com/marketplace/actions/npm-bump"><code>npm-bump</code></a> which can clean up some of the above action boilerplate:</p>
  1592. <pre><code class="hljs language-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">Version</span> <span class="hljs-string">and</span> <span class="hljs-string">Release</span>
  1593.  
  1594. <span class="hljs-attr">on:</span>
  1595.  <span class="hljs-attr">workflow_dispatch:</span>
  1596.    <span class="hljs-attr">inputs:</span>
  1597.      <span class="hljs-attr">newversion:</span>
  1598.        <span class="hljs-attr">description:</span> <span class="hljs-string">&#x27;npm version {major,minor,patch}&#x27;</span>
  1599.        <span class="hljs-attr">required:</span> <span class="hljs-literal">true</span>
  1600.  
  1601. <span class="hljs-attr">env:</span>
  1602.  <span class="hljs-attr">node_version:</span> <span class="hljs-number">14</span>
  1603.  
  1604. <span class="hljs-attr">jobs:</span>
  1605.  <span class="hljs-attr">version_and_release:</span>
  1606.    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
  1607.    <span class="hljs-attr">steps:</span>
  1608.    <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v2.3.2</span>
  1609.      <span class="hljs-attr">with:</span>
  1610.        <span class="hljs-comment"># fetch full history so things like auto-changelog work properly</span>
  1611.        <span class="hljs-attr">fetch-depth:</span> <span class="hljs-number">0</span>
  1612.    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Use</span> <span class="hljs-string">Node.js</span> <span class="hljs-string">$</span>
  1613.      <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-node@v2.1.1</span>
  1614.      <span class="hljs-attr">with:</span>
  1615.        <span class="hljs-attr">node-version:</span> <span class="hljs-string">$</span>
  1616.        <span class="hljs-comment"># setting a registry enables the NODE_AUTH_TOKEN env variable where we can set an npm token.  REQUIRED</span>
  1617.        <span class="hljs-attr">registry-url:</span> <span class="hljs-string">&#x27;https://registry.npmjs.org&#x27;</span>
  1618.    <span class="hljs-bullet">-</span> <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">i</span>
  1619.    <span class="hljs-bullet">-</span> <span class="hljs-attr">run:</span> <span class="hljs-string">npm</span> <span class="hljs-string">test</span>
  1620.    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">npm</span> <span class="hljs-string">version</span> <span class="hljs-string">&amp;&amp;</span> <span class="hljs-string">npm</span> <span class="hljs-string">publish</span>
  1621.      <span class="hljs-attr">uses:</span> <span class="hljs-string">bcomnes/npm-bump@v2.0.1</span>
  1622.      <span class="hljs-attr">with:</span>
  1623.        <span class="hljs-attr">git_email:</span> <span class="hljs-string">bcomnes@gmail.com</span>
  1624.        <span class="hljs-attr">git_username:</span> <span class="hljs-string">$</span>
  1625.        <span class="hljs-attr">newversion:</span> <span class="hljs-string">$</span>
  1626.        <span class="hljs-attr">github_token:</span> <span class="hljs-string">$</span> <span class="hljs-comment"># built in actions token.  Passed tp gh-release if in use.</span>
  1627.        <span class="hljs-attr">npm_token:</span> <span class="hljs-string">$</span> <span class="hljs-comment"># user set secret token generated at npm</span>
  1628. </code></pre>
  1629. <figure class="borderless">
  1630.  <picture>
  1631.    <source srcset="./npm-bump-dark.png" media="(prefers-color-scheme: dark)">
  1632.    <img src="./npm-bump-light.png" alt="Screenshot of npm-bump in the marketplace.">
  1633.  </picture>
  1634.  <figcaption><a href="https://github.com/marketplace/actions/npm-bump"><code>npm-bump</code></a> helps cut down on some of the npm version and release GitHub action boilerplate YAML.  Is it better? Not sure!</figcaption>
  1635. </figure>
  1636. <p>So this is great! You can maintain packages by merging automatically generated pull requests, run your tests on them to ensure package validity, and when you are ready, fully release the package, with a CHANGELOG entry, all from the push of a button on your cell phone. Fully Automated Luxury Package Space Maintenance. 🛰🚽🤳</p>
  1637. <h2 id="level-5%3A-project-generation" tabindex="-1"><span id="level-5"><abbr title="Project Generation">Level 5</abbr></span>: Project generation</h2>
  1638. <p><em>What is the best way to manage all of these independent pieces?</em> A template!  Or, a template repo.</p>
  1639. <p><em>You mean things like <a href="https://yeoman.io">yeoman</a>?</em>  Maybe, though that tool is largely used to ‘scaffold’ <a href="https://github.com/facebook/create-react-app">massive amounts of web framework boilerplate</a> and is a complex ecosystem.</p>
  1640. <p>Something simpler will be more constrained and easier to maintain over time.  <a href="https://docs.github.com/en/free-pro-team@latest/github/creating-cloning-and-archiving-repositories/creating-a-template-repository">Github repo templates</a> and <a href="https://ghub.io/create-project"><code>create-project</code></a> are good choices.</p>
  1641. <h3 id="github-template-repos" tabindex="-1">Github Template Repos</h3>
  1642. <p>Github offers a very simple solution called <a href="https://docs.github.com/en/free-pro-team@latest/github/creating-cloning-and-archiving-repositories/creating-a-template-repository">template repos</a>.  You take any repo on GitHub, go into its settings page and designate it as a template repo.  You can create a new repo from a template repo page with the click of a button, or select it from a drop down in the github <a href="https://github.com/new">create repo wizard</a>.</p>
  1643. <figure class="borderless">
  1644.  <picture>
  1645.    <source srcset="./gh-template-dark.png" media="(prefers-color-scheme: dark)">
  1646.    <img src="./gh-template-light.png" alt="Screenshot of creating a repo from a template.">
  1647.  </picture>
  1648.  <figcaption>Repos that are designated as templates show up in the new repo UI.</figcaption>
  1649. </figure>
  1650. <p>The only issue, is that you then have to go through and modify all the repo specific parameters by hand.
  1651. Better than nothing!
  1652. But we can do better.</p>
  1653. <h3 id="create-project-repos" tabindex="-1"><a href="https://ghub.io/create-project"><code>create-project</code></a> repos</h3>
  1654. <p><a href="https://ghub.io/create-project"><code>create-project</code></a> is a simple CLI tool (by <a href="https://github.com/mafintosh">@mafintosh</a>) that works similar to Github template repos, except it has a `` system that lets you insert values when spawning off a project repo.  You can designate your <a href="https://ghub.io/create-project"><code>create-project</code></a> template repos to also be a Github template repo, and create new projects either way you feel like it.</p>
  1655. <figure>
  1656.  <video controls width="100%" preload="metadata">
  1657.    <source src="./create-project.mp4#t=0.5" type="video/mp4">
  1658.  </video>
  1659.  <figcaption><a href="https://ghub.io/create-project"><code>create-project</code></a> lets you spawn a new project from a git repo and inject values into special <code></code> blocks.</figcaption>
  1660. </figure>
  1661. <p>Here are some of my personal template repos:</p>
  1662. <ul>
  1663. <li><a href="https://github.com/bcomnes/templates/tree/5fd64ef3e919d5e9e0d64bec6fd176c2f746cf1a">bcomnes/templates</a> - simple template for Node.js CJS code.</li>
  1664. <li><a href="https://github.com/bcomnes/esm-template">bcomnes/esm-template</a> - template that includes complexity to publish an ESM module to npm with CJS fall back support. A topic for a future blog post!</li>
  1665. <li><a href="https://github.com/bcomnes/go-template">bcomnes/go-template</a> - a <a href="https://golang.org">Go</a> package boilerplate template that includes versioned dev tooling support.  <code>create-project</code> doesn’t need to only manage boilerplate for Node.js projects.  Maybe a go tool would be better for this, but it does show the flexibility of the tools.</li>
  1666. </ul>
  1667. <h2 id="what-about-docs%3F" tabindex="-1">What about docs?</h2>
  1668. <p>There are various solutions for generating docs from comments that live close to the code.</p>
  1669. <ul>
  1670. <li><a href="https://jsdoc.app">jsdoc</a></li>
  1671. <li><a href="https://typedoc.org">typedoc</a></li>
  1672. <li><a href="https://documentation.js.org">documentation.js</a></li>
  1673. <li><a href="https://medium.com/@trukrs/type-safe-javascript-with-jsdoc-7a2a63209b76">Type Safe JavaScript with JSDoc</a></li>
  1674. <li><a href="https://voxpelli.com/2019/10/use-type-script-3-7-to-generate/">Pelle Wessman on JSDoc-comments</a></li>
  1675. <li><a href="https://doc.deno.land/builtin/stable">deno doc</a></li>
  1676. </ul>
  1677. <p>I haven’t found a solution that satisfies my needs well enough to use one generally.
  1678. Its hard to exceed the quality docs written by hand for and by humans.
  1679. If you have a good solution please let me know!</p>
  1680. <figure class="borderless">
  1681.  <picture>
  1682.    <source srcset="./deno-doc-dark.png" media="(prefers-color-scheme: dark)">
  1683.    <img src="./deno-doc-light.png" alt="Screenshot of deno doc.">
  1684.  </picture>
  1685.  <figcaption><a href="https://doc.deno.land">deno doc</a> is beautiful and keeps code and docs tightly coupled.  But is the human consumed result better? Or does it just lead to cutting corners in exchange for static type checking? Time will tell!</figcaption>
  1686. </figure>
  1687. <h2 id="all-together-now" tabindex="-1">All together now</h2>
  1688. <p>That was a lot to cover.
  1689. If you want to see a complete level 0 through level 5 example, check out my <code>create-template</code> <a href="https://github.com/bcomnes/templates/tree/5fd64ef3e919d5e9e0d64bec6fd176c2f746cf1a">template repo</a> snapshotted to the latest commit of the time of publishing.</p>
  1690. <figure>
  1691.  <video controls width="100%" preload="metadata">
  1692.    <source src="./publish-flow.mp4#t=0.5" type="video/mp4">
  1693.  </video>
  1694.  <figcaption>Here we can see the various parts of level 0-4 automation in action together.  Changes are made to the git history, the module is versioned and published by a bot.  It includes a changelog and was automatically validated by tests.</figcaption>
  1695. </figure>
  1696. <h2 id="final-thoughts" tabindex="-1">Final thoughts</h2>
  1697. <p>This collection is written in the context of the Node.js programming system, however the class of tools discussed apply to every other language ecosystem and these automation levels could serve as a framework for assessing the maturity of automation capabilities of other programming language systems.
  1698. Hopefully they can can provide some insights into the capabilities and common practices around modern JavaScript development for those unfamiliar with this ecosystem.</p>
  1699. <p>Additionally, this documents my personal suite of tools and processes that I have developed to automate package maintenance, and is by no means normative.  Modification and experimentation is always encouraged.</p>
  1700. <p>There are many subtle layers to the Node.js programming system, and this just covers the maintenance automation layer that can exist around a package.
  1701. Much more could be said about the versioned development tooling, standardized scripting hooks, diamond dependency problem solutions, localized dependencies, upstream package hacking/debugging conveniences and local package linking.  An even deeper dive could be made on the overlap these patterns have (and don’t) have in other JS runtimes like <a href="https://deno.com">Deno</a> which standardizes a lot around <abbr title="Automated Tests + CI">Level 1</abbr>, or even other languages like Go or Rust.</p>
  1702. <p>If you enjoyed this article, have suggestions or feedback, or think I’m full of it, follow me on twitter (<a href="https://twitter.com/bcomnes">@bcomnes</a>) and feel free to hop in the the accompanying thread.  I would love to hear your thoughts, ideas and examples!  Also subscribe to my <a href="http://bret-dk.local:3000/feed.xml">RSS</a>/<a href="http://bret-dk.local:3000/feed.json">JSON</a> Feed in your favorite RSS reader.</p>
  1703. <blockquote class="twitter-tweet"><p lang="en" dir="ltr">&quot;Fully Automated Luxury Space Age Package Maintenance&quot;<br><br>I wrote up how tedious package maintenance tasks can be fully automated. <br><br>Hope someone enjoys!<a href="https://t.co/fvYIu2Wq0r">https://t.co/fvYIu2Wq0r</a> <a href="https://t.co/q220LTax8X">pic.twitter.com/q220LTax8X</a></p>&mdash; 🌌🌵🛸Bret🏜👨‍👩‍👧🚙 (@bcomnes) <a href="https://twitter.com/bcomnes/status/1311034520305569800?ref_src=twsrc%5Etfw">September 29, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
  1704. <h3 id="syndications" tabindex="-1">Syndications</h3>
  1705. <ul>
  1706. <li><a href="https://www.reddit.com/r/node/comments/j27pbt/fully_automated_luxury_space_age_package/" rel="syndication" class="u-syndication">/r/node</a></li>
  1707. <li><a href="https://github.community/t/fully-automated-luxury-space-age-package-maintenance-with-github-actions/135103" rel="syndication" class="u-syndication">Github Community Forum</a></li>
  1708. <li><a href="https://twitter.com/bcomnes/status/1311034520305569800" rel="syndication" class="u-syndication">twitter thread</a></li>
  1709. </ul>
  1710. <h3 id="see-also" tabindex="-1">See also</h3>
  1711. <ul>
  1712. <li><a href="https://www.swyx.io/release-automation">swyx.io/release-automation</a></li>
  1713. </ul>
  1714. <hr class="footnotes-sep">
  1715. <section class="footnotes">
  1716. <ol class="footnotes-list">
  1717. <li id="fn1" class="footnote-item"><p>Just as a refresher, <code>npm version</code> is a command that bumps the version in <code>package.json</code>, creates a commit with that change titled <code>0.0.0</code>, then tags it <code>v0.0.0</code>. See <a href="https://docs.npmjs.com/misc/scripts">npm</a> docs for more info. <a href="#fnref1" class="footnote-backref">↩︎</a></p>
  1718. </li>
  1719. </ol>
  1720. </section>
  1721. ]]></content>
  1722.    <link rel="alternate" href="https://bret.io/projects/package-automation/"/>
  1723.  </entry>
  1724.  <entry>
  1725.    <id>https://bret.io/jobs/netlify/#2020-01-16T22:19:35.001Z</id>
  1726.    <title>Netlify Portfolio</title>
  1727.    <updated>2020-01-16T22:19:35.001Z</updated>
  1728.    <published>2020-01-16T22:19:35.001Z</published>
  1729.    <author>
  1730.      <name>Bret Comnes</name>
  1731.      <uri>https://bret.io</uri>
  1732.    </author>
  1733.    <content type="html"><![CDATA[<p>I was lucky to be able to contribute to many features and experiences that affected <a href="https://www.netlify.com">Netlify</a>’s massive user base.  Here are some examples of things that I worked on.  If this kind of work looks interesting to you, Netlify is a fantastic team to join: <a href="https://www.netlify.com/careers/">netlify.com/careers</a>.  Questions and comments can be sent via email or <a href="https://twitter.com/bcomnes/status/1111324728277368832">twitter</a>.</p>
  1734. <h2 id="platform" tabindex="-1">Platform</h2>
  1735. <p>After working on the Product team for slightly over a year, I switched to working on Netlify’s platform team.  The team has a strong DevOps focus and maintains a redundant, highly available multi-cloud infrastructure on which Netlify and and all of its services run.  My focus on the team is to maintain, develop, scale and improve various critical services and libraries.  Below are some examples of larger projects I worked on.</p>
  1736. <h3 id="buildbot" tabindex="-1">Buildbot</h3>
  1737. <p>One of my primary responsibilities upon joining the team is to maintain the Buildbot that all customer site builds run on.  It is partially open-source so customers can explore the container in a more freeform matter locally.</p>
  1738. <ul>
  1739.  <li class="lang docker"><a href="https://github.com/netlify/build-image">netlify/build-image</a> - Docker file which defines the container builds run in.</li>
  1740.  <li class="lang go"><a href="https://pkg.go.dev/github.com/netlify/open-api/go">netlify/open-api/go</a> - Go open-api client used in the bot.</li>
  1741. </ul>
  1742. <p><a href="./buildbot.png"><img src="./buildbot.png" alt="screenshot of buildbot logs"></a></p>
  1743. <h4 id="selectable-build-images" tabindex="-1"><a href="https://www.netlify.com/blog/2019/03/14/a-more-flexible-build-architecture-with-updated-linux/">Selectable build images</a></h4>
  1744. <p>One of the first feature additions I launched for the Buidlbot was selectable build images.  This project required adding the concept of additional build images to the API and UI and to develop an upgrade path allowing users to migrate their websites to the new build image image wile also allowing them to roll back to the old image if they needed more time to accommodate the update.</p>
  1745. <p>Additionally, I performed intake on a number of user contributed build-image additions and merged other various potential breaking changes within a development window before releasing.  I also helped develop the changes to the Ruby on Rails API, additions to the React UI, as well as write the user documentation.  It was widely cross cutting project.</p>
  1746. <p><a href="https://www.netlify.com/blog/2019/03/14/a-more-flexible-build-architecture-with-updated-linux/"><img src="./build-image-blog.png" alt="screenshot of buildbot image blogpost"></a></p>
  1747. <p><a href="./image-selection.png"><img src="./image-selection.png" alt="screenshot of buildbot settings"></a></p>
  1748. <h3 id="open-api" tabindex="-1"><a href="https://open-api.netlify.com">Open API</a></h3>
  1749. <p>I help maintain and further develop Netlify’s Open-API (aka Swagger) API definition, website and surrounding client ecosystem.  While open-api can be cumbersome, it has been a decent way to synchronize projects written in different language ecosystems in a unified way.</p>
  1750. <ul>
  1751.  <li class="lang go"><a href="https://github.com/netlify/open-api">netlify/open-api</a> - API definition and Go client</li>
  1752.  <li class="lang js"><a href="https://github.com/netlify/js-client">netlify/js-client</a> - Node.JS and Browser JS client</li>
  1753. </ul>
  1754. <p><a href="https://open-api.netlify.com"><img src="./open-api-web.png" alt="screenshot of the open-api website"></a></p>
  1755. <h3 id="other-platform-projects-i-work-on" tabindex="-1">Other platform projects I work on</h3>
  1756. <ul>
  1757.  <li class="lang go"><a href="https://github.com/netlify/gotrue">netlify/gotrue</a> - Netlify's Identity service</li>
  1758.  <li class="lang go"><a href="https://github.com/netlify/gocommerce">netlify/gocommerce</a> - Netlify's Commerce service</li>
  1759.  <li class="lang js"><a href="https://github.com/netlify/zip-it-and-ship-it">netlify/zip-it-and-ship-it</a> - Netlify's Lambda function packaging algorithm</li>
  1760. </ul>
  1761. <h2 id="product" tabindex="-1">Product</h2>
  1762. <p>I worked on Neltify’s Product team for a bit over a year and completed many successful user facing projects.  Here are just a few examples:</p>
  1763. <h3 id="cli" tabindex="-1"><a href="https://cli.netlify.com">CLI</a></h3>
  1764. <p>I was primary author on Netlify’s current CLI codebase.</p>
  1765. <ul>
  1766.  <li class="lang js"><a href="https://github.com/netlify/cli">netlify/cli</a> - Netlify's extensible CLI</li>
  1767.  <li class="lang js"><a href="https://github.com/netlify/js-client">netlify/js-client</a> - The API client used to make all API calls in the CLI</li>
  1768.  <li class="lang js"><a href="https://github.com/netlify/cli-utils">netlify/cli-utils</a> - A common utility class for loading and saving configuration from a CLI command</li>
  1769.  <li class="lang js"><a href="https://github.com/bcomnes/netlify-lambda-builder">bcomnes/netlify-lambda-builder</a> - I wrote a CLI that packaged Netlify functions in a convenient manner in order to communicate the idea clearly as a possible direction to take.</li>
  1770. </ul>
  1771. <figure>
  1772.  <video controls width="100%" preload="metadata">
  1773.    <source src="./netlify-cli.mp4#t=0.5" type="video/mp4">
  1774.  </video>
  1775.  <figcaption>Build image selection UI.</figcaption>
  1776. </figure>
  1777. <p><a href="https://www.netlify.com/blog/2018/09/10/netlify-cli-2.0-now-in-beta/"><img src="./cli-blog.png" alt="screenshot of the cli announcement blog post"></a></p>
  1778. <h3 id="jamstack-slides" tabindex="-1"><a href="https://brets-jamstack-pdx-slides.netlify.app/#0">JAMStack slides</a></h3>
  1779. <p>I gave a talk on some ideas and concepts I came across working with Netlify’s platform and general JAMStack architecture at a local Portland JAMStack meetup.</p>
  1780. <ul>
  1781.  <li class="lang html"><a href="https://github.com/bcomnes/jamstack-pdx-slides">bcomnes/jamstack-pdx-slides</a></li>
  1782. </ul>
  1783. <p><a href="https://brets-jamstack-pdx-slides.netlify.app/#0"><img src="./jam-slides.png" alt="jamstack-slides"></a></p>
  1784. <h3 id="domains" tabindex="-1"><a href="https://www.netlify.com/blog/2018/06/19/buy-and-secure-a-custom-domain-through-netlify/">Domains</a></h3>
  1785. <p>I led the Netlify Domains project which allowed users to to add an existing live domain during site setup, or buy the domain if it is available.  This feature enabled users to deploy a website from a git repo to a real live domain name with automatic https in a matter of minutes and has resulted in a nice stream of ever increasing AAR for the company.</p>
  1786. <p><a href="https://www.netlify.com/blog/2018/06/19/buy-and-secure-a-custom-domain-through-netlify/"><img src="./domains-blogpost.png" alt="domains blogpost"></a></p>
  1787. <h3 id="build-status-favicons" tabindex="-1"><a href="https://www.netlify.com/blog/2018/05/22/netlify-now-shows-your-deploy-status-on-its-favicon/">Build Status Favicons</a></h3>
  1788. <p>I helped lead and implement build status favicons, so you can put a build log into a tab, and monitor status from the tab bar.</p>
  1789. <p><a href="https://www.netlify.com/blog/2018/05/22/netlify-now-shows-your-deploy-status-on-its-favicon/"><img src="./build-icons.jpeg" alt="build status icons"></a></p>
  1790. <h3 id="lambda-functions" tabindex="-1"><a href="https://www.netlify.com/blog/2018/03/20/netlifys-aws-lambda-functions-bring-the-backend-to-your-frontend-workflow/">Lambda Functions</a></h3>
  1791. <p>I implemented the application UI for Netlify’s Lambda functions and logging infrastructure, and have continued to help design and improve the developer ergonomics of the Lambda functions feature set.</p>
  1792. <p><a href="https://www.netlify.com/blog/2018/03/20/netlifys-aws-lambda-functions-bring-the-backend-to-your-frontend-workflow/"><img src="./functions.png" alt="functions"></a></p>
  1793. <h3 id="identity-widget" tabindex="-1"><a href="https://identity.netlify.com">Identity Widget</a></h3>
  1794. <p>I helped architect and implement Netlify’s Identity widget.</p>
  1795. <p><a href="https://identity.netlify.com"><img src="./identity-widget.png" alt="identity widget"></a></p>
  1796. <h3 id="dashboard" tabindex="-1"><a href="https://www.netlify.com/blog/2017/08/22/introducing-site-dashboards/">Dashboard</a></h3>
  1797. <p>I helped implement the UI for our site dashboard redesign.</p>
  1798. <p><a href="https://www.netlify.com/blog/2017/08/22/introducing-site-dashboards/"><img src="./dashboard.png" alt="dashboard"></a></p>
  1799. <h3 id="security-audit-log" tabindex="-1"><a href="https://www.netlify.com/blog/2017/07/27/introducing-audit-log/">Security Audit Log</a></h3>
  1800. <p>I led the project to specify and implement the Audit Log for teams and identity instances.</p>
  1801. <p><a href="https://www.netlify.com/blog/2017/07/27/introducing-audit-log/"><img src="./audit-log.png" alt="audit log screenshot"></a></p>
  1802. <h3 id="split-testing" tabindex="-1"><a href="https://www.netlify.com/blog/2017/06/28/introducing-teams-new-features-and-an-update-to-our-plans/">Split Testing</a></h3>
  1803. <p>I implemented the UI for Netlify’s split testing feature.</p>
  1804. <video controls width="100%" preload="metadata">
  1805.  <source src="./split-testing.mp4#t=0.5" type="video/mp4">
  1806. </video>
  1807. ]]></content>
  1808.    <link rel="alternate" href="https://bret.io/jobs/netlify/"/>
  1809.  </entry>
  1810.  <entry>
  1811.    <id>https://bret.io/projects/websockets/#2019-10-29T18:50:05.140Z</id>
  1812.    <title>Websockets</title>
  1813.    <updated>2019-10-29T18:50:05.140Z</updated>
  1814.    <published>2019-10-29T18:50:05.140Z</published>
  1815.    <author>
  1816.      <name>Bret Comnes</name>
  1817.      <uri>https://bret.io</uri>
  1818.    </author>
  1819.    <content type="html"><![CDATA[<p>Websockets are a fantastic and underutilized API.  Here are some tools and experiments I built to make working with websockets a bit nicer.</p>
  1820. <h2 id="universal-reconnecting-websocket" tabindex="-1"><a href="https://github.com/bcomnes/universal-reconnecting-websocket"><code>universal-reconnecting-websocket</code></a></h2>
  1821. <p><a href="https://github.com/bcomnes/universal-reconnecting-websocket/"><img src="urws.png" alt=""></a></p>
  1822. <p>Universal reconnecting websocket is a thin websocket client wrapper on top of DOM <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket"><code>WebSocket</code></a>’s and Node.js’s <a href="https://github.com/websockets/ws">ws</a>.  It provides the following features:</p>
  1823. <ul>
  1824. <li>Automatic serializing of messages</li>
  1825. <li>Automatic reconnect, with customizable strategies</li>
  1826. <li>Binary support</li>
  1827. <li>Fully defined state machine</li>
  1828. <li>Useful connection state events</li>
  1829. <li>Message and error handling</li>
  1830. <li>Unified Node.js and Browser API</li>
  1831. </ul>
  1832. <p>The idea behind this library was to bring the wrapper API that I desired for websockets to both Node.js and Browsers in a unified way.  It turns out, that there are a bunch of subtle but incompatible API differences between the <code>ws</code> client and the one found in the browser, so ultimately, I was a bit disappointed with how this experiment turned out.</p>
  1833. <p>I was very happy with the browser side of the API.  I think this part turned out great, and was what the surface API was designed against.</p>
  1834. <p>The Node.js API was essentially mixed on top of the API that was implemented against the DOM api.  The <code>ws</code> client has a lot more options than Browser websockts, and a number of key differences around protocol and binary options, which would require one to write two sepearate clients for Node and Browsers which kind of defeated the purpose of a universal client.  I did not realize this until after I wrote it.</p>
  1835. <p>The next step for this project would be to implement two specific <code>abstract</code> reconnecting sockets with the same API, but implemented and documented specifically to the socket type it is written around.</p>
  1836. <h3 id="key-takeway" tabindex="-1">Key Takeway</h3>
  1837. <p>Don’t try to write a “universal” module when wrapping <code>io</code> primitives.  Instead, implement an <code>abstract</code> version of the api specific to the <code>io</code> primitives it uses.  If, down the road, it is determined to be beneficial to have a single module that automatically chooses which underlying <code>abstract</code> implementation to use based on the environment, you can use the singular <code>abstract</code> implementations to achieve that.  Universal modules should usually only be for algorithmic modules.</p>
  1838. <h2 id="websocket-chat" tabindex="-1"><a href="https://github.com/bcomnes/websocket-chat"><code>websocket-chat</code></a></h2>
  1839. <p><a href="https://github.com/bcomnes/websocket-chat"><img src="./websocket-chat.png" alt=""></a></p>
  1840. <p>Websocket chat is a prototype grade chat server using <a href="https://github.com/websockets/ws"><code>ws</code></a>.  It implements a few demonstration features:</p>
  1841. <ul>
  1842. <li>Keepalive Heartbeat</li>
  1843. <li>Message broadcast</li>
  1844. <li>Echo server</li>
  1845. <li>Chat room messages</li>
  1846. <li>Nickname support</li>
  1847. <li>Websocket server testing</li>
  1848. </ul>
  1849. <p>It’s not much but it acts as a handy reference on how to implement a naive chat server with limited features.  It is hosted on Heroku and is completely separate from the client, other than the protocol assumptions.</p>
  1850. <h2 id="websocket-chat-client" tabindex="-1"><a href="https://github.com/bcomnes/websocket-chat-client"><code>websocket-chat-client</code></a></h2>
  1851. <p><a href="https://websocket-chat-client.netlify.com"><img src="./websocket-chat-client.jpg" alt="Screenshot of websocket client UI"></a></p>
  1852. <ul>
  1853. <li>🌎<a href="https://websocket-chat-client.netlify.com">Live Demo</a></li>
  1854. <li>🛠<a href="https://github.com/bcomnes/websocket-chat-client">Code</a></li>
  1855. </ul>
  1856. <p>This is a prototype grade example of a real time chat app that uses <code>universal-reconnecting-websocket</code> and connects to the <code>websocket-chat</code> server.  The application is completely static, and can connect to any arbitrary <code>websocket-chat</code> server, demonstrating a <a href="https://jamstack.org">JAMStack</a> architecture.  Keeping the client and server decoupled ensures you have to take care of all protocol assumptions up front which would help ensure you could implement other clients against the same server (for example, a native mobile app client).</p>
  1857. <h2 id="dom-event-handler" tabindex="-1"><a href="https://github.com/bcomnes/dom-event-handler"><code>dom-event-handler</code></a></h2>
  1858. <p><a href="https://github.com/bcomnes/dom-event-handler"><img src="./dom-event-handler.png" alt=""></a></p>
  1859. <p>This is module of a <a href="https://webreflection.medium.com/dom-handleevent-a-cross-platform-standard-since-year-2000-5bf17287fd38">WebReflection article</a> discussing the ancient and often forgotten detail of the <a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener">EventLister</a> api.  Unfortunately the author of that article would probably disapprove of this module due to lack of semicolons.  C’est la vie.  (I still have mad respect for your work Andrea!)</p>
  1860. <p>Essentially: it lets you write class methods and use them as event handler functions, without binding.  This has some performance implications if you have many event handler functions, and arguably some ergonomic gains.</p>
  1861. <p>These are two functionally equivalent implementations:</p>
  1862. <pre><code class="hljs language-js"><span class="hljs-keyword">const</span> <span class="hljs-title class_">DOMEventHandler</span> = <span class="hljs-built_in">require</span>(<span class="hljs-string">&quot;dom-event-handler&quot;</span>)
  1863.  
  1864. <span class="hljs-keyword">class</span> <span class="hljs-title class_">MyWSController</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_ inherited__">SomeOtherClass</span> {
  1865.  <span class="hljs-title function_">constructor</span> () {
  1866.    <span class="hljs-variable language_">this</span>.<span class="hljs-property">ws</span> = <span class="hljs-keyword">new</span> <span class="hljs-title class_">WebSocket</span>(<span class="hljs-string">&#x27;ws://localhost:8080&#x27;</span>)
  1867.    <span class="hljs-variable language_">this</span>.<span class="hljs-property">handler</span> = <span class="hljs-keyword">new</span> <span class="hljs-title class_">DOMEventHandler</span>(<span class="hljs-variable language_">this</span>, <span class="hljs-variable language_">this</span>.<span class="hljs-property">ws</span>)
  1868.  }
  1869.  
  1870.  <span class="hljs-comment">// These methods handle the websocket events</span>
  1871.  onmessage (ev) {}
  1872.  onopen (ev) {}
  1873.  onerror (ev) {}
  1874.  onclose (ev) {}
  1875. }
  1876. </code></pre>
  1877. <p>vs…</p>
  1878. <pre><code class="hljs language-js"><span class="hljs-keyword">const</span> ws = <span class="hljs-keyword">new</span> <span class="hljs-title class_">WebSocket</span>(<span class="hljs-string">&#x27;ws://localhost:8080&#x27;</span>)
  1879.  
  1880. <span class="hljs-keyword">class</span> <span class="hljs-title class_">VerboseWSController</span> {
  1881.  <span class="hljs-title function_">constructor</span> () {
  1882.    <span class="hljs-variable language_">this</span>.<span class="hljs-property">foo</span> = <span class="hljs-string">&#x27;bar&#x27;</span>
  1883.    <span class="hljs-variable language_">this</span>.<span class="hljs-property">onmessage</span> = <span class="hljs-variable language_">this</span>.<span class="hljs-property">onmessage</span>.<span class="hljs-title function_">bind</span>(<span class="hljs-variable language_">this</span>)
  1884.    <span class="hljs-variable language_">this</span>.<span class="hljs-property">onopen</span> = <span class="hljs-variable language_">this</span>.<span class="hljs-property">onopen</span>.<span class="hljs-title function_">bind</span>(<span class="hljs-variable language_">this</span>)s
  1885.    <span class="hljs-variable language_">this</span>.<span class="hljs-property">onerror</span> = <span class="hljs-variable language_">this</span>.<span class="hljs-property">onerror</span>.<span class="hljs-title function_">bind</span>(<span class="hljs-variable language_">this</span>)
  1886.    <span class="hljs-variable language_">this</span>.<span class="hljs-property">onclose</span> = <span class="hljs-variable language_">this</span>.<span class="hljs-property">onclose</span>.<span class="hljs-title function_">bind</span>(<span class="hljs-variable language_">this</span>)
  1887.  }
  1888.  
  1889.  onmessage (ev) {}
  1890.  onopen (ev) {}
  1891.  onerror (ev) {}
  1892.  onclose (ev) {}
  1893. }
  1894.  
  1895. <span class="hljs-keyword">const</span> c = <span class="hljs-keyword">new</span> <span class="hljs-title class_">VerboseWSController</span>()
  1896.  
  1897. ws.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">&#x27;message&#x27;</span>, c.<span class="hljs-property">onmessage</span>)
  1898. ws.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">&#x27;open&#x27;</span>, c.<span class="hljs-property">onopen</span>)
  1899. ws.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">&#x27;error&#x27;</span>, c.<span class="hljs-property">onerror</span>)
  1900. ws.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">&#x27;close&#x27;</span>, c.<span class="hljs-property">onclose</span>)
  1901. </code></pre>
  1902. <p>Isn’t that nice?</p>
  1903. <h2 id="node-event-handler" tabindex="-1"><a href="https://github.com/bcomnes/node-event-handler"><code>node-event-handler</code></a></h2>
  1904. <p><a href="https://github.com/bcomnes/node-event-handler"><img src="./node-event-handler.png" alt=""></a></p>
  1905. <p>When implementing <code>universal-reconnecting-websocket</code>, it was assumed that the <a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener">EventLister</a> api could be mocked for the <code>ws</code> client events.  It turns out, this wasn’t easy.</p>
  1906. <p>Node.js and DOM event systems are just too different.  Here some some challenges in making them overlap:</p>
  1907. <ul>
  1908. <li>Node.js events don’t support the <code>handleEvent</code> method, like DOM events.</li>
  1909. <li>The shape of the event data objects are totally different in both systems.</li>
  1910. <li>DOM events are a large API and difficult to simulate in Node.js.</li>
  1911. </ul>
  1912. <p>These differences broguht me to the following conclusions.</p>
  1913. <ul>
  1914. <li>When Node.js was the hot new thing, it was in vogue to implement Node compatible API layers for the browser.  It was usually straight forward, but inevitably a userspace solution.  This had a lot of advantages (like nice and simple APIs that worked effectively) and little drawbacks other than a bit of extra bundle size and lack of a standards authority dictating how things should work.  <a href="http://browserify.org"><code>browserify</code></a> used this strategey to great effect, and it still works great today.</li>
  1915. <li>As Node.js aged, and its opponents slowly regained power to push back against its influence, and as Node.js’s innovations slowly sublimated into implemented, yet incompatible “standards”, it became fashionable to implement DOM apis compatible for node.  The sudden interest in <a href="https://github.com/node-fetch/node-fetch">node-fetch</a> is testament to this trend, despite many <a href="https://github.com/search?q=repo%3Anode-fetch%2Fnode-fetch+clone+&amp;type=issues">bugs</a>, and awkward differences between it and the real DOM API.</li>
  1916. <li>Porting Node.js APIs to the browser is easy, since they are fundamentally simple, userspace derived APIs.</li>
  1917. <li>Porting Browser APIs to Node in userspace is not easy, complex and error prone.  Avoid doing it.  See the key takeaways from <code>urws</code> above for the proper way to handle IO abstractions.</li>
  1918. <li>Node events are simpler to understand, but lack the handle event API found in the DOM.  Maybe a userspace event system could accommodate this a bit better, and still remain simple and compatible with Node.js events.</li>
  1919. <li>DOM events are <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event">complex</a> and way more difficult to comprehend, especially for newbies.  The handle event method is a neat idea to avoid binding, but re-introducing the complexity of DOM events to node is not a good idea.</li>
  1920. </ul>
  1921. ]]></content>
  1922.    <link rel="alternate" href="https://bret.io/projects/websockets/"/>
  1923.  </entry>
  1924. </feed>

If you would like to create a banner that links to this page (i.e. this validation result), do the following:

  1. Download the "valid Atom 1.0" banner.

  2. Upload the image to your own server. (This step is important. Please do not link directly to the image on this server.)

  3. Add this HTML to your page (change the image src attribute if necessary):

If you would like to create a text link instead, here is the URL you can use:

http://www.feedvalidator.org/check.cgi?url=https%3A//bret.io/feed.xml

Copyright © 2002-9 Sam Ruby, Mark Pilgrim, Joseph Walton, and Phil Ringnalda