<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Hopefully Surprising Blog RSS Feed]]></title><description><![CDATA[Hopefully Surprising Blog RSS Feed]]></description><link>http://github.com/dylang/node-rss</link><generator>GatsbyJS</generator><lastBuildDate>Thu, 18 Jun 2026 15:22:23 GMT</lastBuildDate><atom:link href="https://hopefullysurprising.com/rss.xml" rel="self" type="application/rss+xml"></atom:link><item><title><![CDATA[AI-First, Engineer-Led]]></title><description><![CDATA[Now that the machine writes the code well, the engineer's craft moves to the floor the machine works on. I became an engineer because I like…]]></description><link>https://hopefullysurprising.com/ai-first-engineer-led/</link><guid isPermaLink="false">https://hopefullysurprising.com/ai-first-engineer-led/</guid><pubDate>Tue, 16 Jun 2026 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;span&gt;
      &lt;span&gt;
    &lt;span&gt;&lt;/span&gt;
  &lt;img alt=&quot;An architectural blueprint of a factory production line&quot; src=&quot;https://hopefullysurprising.com/static/545569dd3f7079b25f59304d22187c3e/f058b/factory-floor.png&quot; /&gt;
  &lt;/span&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Now that the machine writes the code well, the engineer&apos;s craft moves to the floor the machine works on.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I became an engineer because I like building things that work — and, hopefully, things that are a little remarkable. The code was always how I got there; it was never the point of it. So when a machine arrives that can write that code, and write it well, it doesn&apos;t take the craft away. It gives me a better question: now that the machine is this good, how do I get the most out of it — more built, and built better?&lt;/p&gt;
&lt;p&gt;This is exactly the kind of problem I enjoy most. I design the thing that works — the structure, the constraints, the way the parts fit. When the machine takes over the writing, that instinct doesn&apos;t go away; it moves to where it matters most now, which is the environment the machine works in.&lt;/p&gt;
&lt;p&gt;There&apos;s a name people are starting to use for this work — &lt;em&gt;harness engineering&lt;/em&gt; — and the idea behind it is good: an agent does better when you design the environment it builds inside, instead of just giving it a prompt and hoping for the best. Give it the real shape of the code, clear constraints, and a way to check itself, and the output stops being a gamble. If a machine fails, it&apos;s my problem.&lt;/p&gt;
&lt;p&gt;A picture from the real world helps here. Think of the engineer who runs a factory floor. The machines do the making; the engineer owns the line — its layout, its power, the materials feeding each station, the order the work moves through. Nobody measures that engineer by how fast they can turn a single bolt. They are measured by how well the whole line runs once they step back from it. That is the move I&apos;m making: from turning bolts to designing the floor.&lt;/p&gt;
&lt;p&gt;And a floor works only if two things are true. Most of the talk is about the first, and skips the second.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I have to see it clearly enough to shape it, and the machine has to reach into it freely, without a locked door at every turn.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The first keeps me in charge — I can&apos;t improve a station I&apos;ve never looked at. The second is the one that matters most to me. If the agent can only reach a tool through a gate, a wrapper, or a rate limit, that tool might as well not be there at all. An agent boxed into a corner is a weak one; a strong one can move freely, with the same reach I&apos;d give a trusted colleague.&lt;/p&gt;
&lt;p&gt;In software, the busiest part of that floor is the command line. It&apos;s where the most capable machines plug in — open, scriptable, asking no one for permission — and it&apos;s also the hardest part to see all at once: hundreds of tools, each one a maze of subcommands and flags, most of it behind a help page you have to go looking for. The agent reaches all of it freely; I can barely remember it all. Closing that small gap is what made me build a little tool (that I called &quot;Bract&quot;), which I&apos;ve written about &lt;a href=&quot;https://hopefullysurprising.com/meet-bract&quot;&gt;separately&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The direction is clear, and I&apos;ll be honest about how I feel about it: for me, the craft has never been more exciting. The machine will always type faster than I can — and that is exactly what frees me for the bigger, longer-lasting work of designing the place where good software gets made. Build the floor well, keep the right doors unlocked, and let the machines run. It&apos;s a lot of fun.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Meet Bract]]></title><description><![CDATA[A small terminal tool that makes your CLI toolchain explorable — and the one fortunate fact behind it. In the previous post I argued that…]]></description><link>https://hopefullysurprising.com/meet-bract/</link><guid isPermaLink="false">https://hopefullysurprising.com/meet-bract/</guid><pubDate>Tue, 16 Jun 2026 11:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;A small terminal tool that makes your CLI toolchain explorable — and the one fortunate fact behind it.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In the &lt;a href=&quot;https://hopefullysurprising.com/ai-first-engineer-led&quot;&gt;previous post&lt;/a&gt; I argued that the environment an AI works in has to stay visible to whoever shapes it — and that, in software, the command line is one of the harder parts to see. Let me show you the small tool I built for exactly that.&lt;/p&gt;
&lt;p&gt;You know the wall I mean. You open a powerful command you don&apos;t use every day — &lt;code&gt;kubectl&lt;/code&gt;, say, or the Azure CLI — and to find the one thing you need, you climb down through help page after help page, subcommand into subcommand, putting the right line together one flag at a time. The power is all there. Seeing it is the hard part.&lt;/p&gt;
&lt;p&gt;Bract is my attempt to make it visible: you browse your toolchain like a file tree, fill a command in through a form, and copy or run it.&lt;/p&gt;
&lt;img src=&quot;https://hopefullysurprising.com/demo-bract.gif&quot; alt=&quot;Browsing a CLI toolchain as file-explorer columns and filling a guided kubectl form in Bract&quot; /&gt;
&lt;p&gt;It doesn&apos;t run just anywhere, and that&apos;s on purpose. Bract reads your &lt;a href=&quot;https://mise.jdx.dev&quot;&gt;mise&lt;/a&gt; environment — and if you haven&apos;t met mise yet, you&apos;re in for a treat: it&apos;s a tool manager that pins the exact CLIs, runtimes, and tasks a project needs, so the toolchain is the same for everyone who opens the repo. mise decides what&apos;s on your factory floor; Bract is how you walk it.&lt;/p&gt;
&lt;p&gt;Now, the obvious worry about a tool like this is coverage. There are thousands of command-line programs out there, all different, and teaching one little browser to understand each of them by hand would be hopeless. The fortunate fact is that it doesn&apos;t have to.&lt;/p&gt;
&lt;p&gt;A lot of modern CLIs aren&apos;t hand-built anymore; they&apos;re generated by a few frameworks, and every tool built on the same framework has the same help-text shape. So you teach the framework once, and every tool built on it comes into view together. A single parser for Cobra doesn&apos;t just read &lt;code&gt;kubectl&lt;/code&gt; — it reads &lt;code&gt;helm&lt;/code&gt;, &lt;code&gt;gh&lt;/code&gt;, and the hundreds of others built the same way.&lt;/p&gt;
&lt;p&gt;I reckon this luck is recent, and it doesn&apos;t cover everything. The older tools were each written from scratch, so this approach can&apos;t reach them — even though some of them are the most loved tools on the system. It&apos;s uneven in another way, too: Cobra alone opens a huge family of tools, while a parser for Microsoft&apos;s Knack covers little more than the Azure CLI, and Usage covers mise itself and a small, hand-picked set. The idea only works where a framework became popular — but where it did, it works very well.&lt;/p&gt;
&lt;p&gt;Bract has been really useful in my own work, and I&apos;m putting it out because I honestly believe you could find it useful. I recommend giving it a run; the &lt;a href=&quot;https://github.com/hopefullysurprising/bract&quot;&gt;repository&lt;/a&gt; has everything else, including where to tell me what you find. I think you&apos;ll enjoy finally seeing the whole floor from a new perspective.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Tyk Gateway with OpenAPI Architecture: Comprehensive Guide for Free Tyk & PostgREST Setup]]></title><description><![CDATA[Recently, in one of my projects, I have been working on an API solution providing access to data through REST endpoints. Since data has been…]]></description><link>https://hopefullysurprising.com/oas-api-gateway-with-tyk/</link><guid isPermaLink="false">https://hopefullysurprising.com/oas-api-gateway-with-tyk/</guid><pubDate>Fri, 16 Feb 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;span&gt;
      &lt;span&gt;
    &lt;span&gt;&lt;/span&gt;
  &lt;img alt=&quot;Reverse proxy&quot; src=&quot;https://hopefullysurprising.com/static/fdf996290dc55dcf37984c07e51c0f9a/f058b/reverse-proxy.png&quot; /&gt;
  &lt;/span&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Recently, in one of my projects, I have been working on an API solution providing access to data through REST endpoints. Since data has been structured in a relational database (Postgres) and there is a requirement to be compliant with &lt;a href=&quot;https://www.openapis.org&quot;&gt;OpenAPI specification&lt;/a&gt;, I was naturally inclined towards choosing &lt;a href=&quot;https://postgrest.org/en/stable/index.html&quot;&gt;PostgREST&lt;/a&gt; as API server solution. That would provide quite flexible methods of interacting with the underlying dataset without writing much (if any) code for endpoints. Additionally, OAS compliance is &lt;a href=&quot;https://postgrest.org/en/stable/references/api/openapi.html&quot;&gt;available out of the box&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While a PostgREST instance would cover majority of our needs, some functionality would be quite challenging to implement with a single PostgREST instance. To leave some room for future expansions, I have chosen to use &lt;a href=&quot;https://tyk.io/open-source-api-gateway/&quot;&gt;Tyk gateway&lt;/a&gt; to be my &lt;a href=&quot;https://en.wikipedia.org/wiki/Reverse_proxy&quot;&gt;reverse proxy&lt;/a&gt; so that it orchestrates routing the requests to a correct upstream API node. It&apos;s available in open-source version, and while lion&apos;s share of their documentation is focused on paid Tyk Dashboard, from the functionality perspective, &lt;a href=&quot;https://github.com/TykTechnologies/tyk&quot;&gt;open-source Tyk gateway&lt;/a&gt; is complete.&lt;/p&gt;
&lt;p&gt;In this article, I&apos;m sharing experience of how to use advantages of OpenAPI specification for configuring a functional API gateway. Also, I will demonstrate how to overcome challenges that occur when doing so for PostgREST (or other OAS-based) instances.&lt;/p&gt;
&lt;p&gt;As a bonus, I&apos;m showing the process using an example of two separate REST APIs. A solution that fully benefits from using an API gateway.&lt;/p&gt;
&lt;p&gt;Your use case might be different but the target architecture could be similar.&lt;/p&gt;
&lt;h2&gt;Vision&lt;/h2&gt;
&lt;p&gt;&lt;span&gt;
      &lt;span&gt;
    &lt;span&gt;&lt;/span&gt;
  &lt;img alt=&quot;Solution diagram&quot; src=&quot;https://hopefullysurprising.com/static/2745605de416fa7fab960b0bbd2fd775/f058b/solution-diagram.png&quot; /&gt;
  &lt;/span&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;To leverage the advantages of conformity with OpenAPI specification (especially, predefined API schema), I decided to use OAS definitions as the source of truth for publishing API capabilities to external parties and to the API gateway. Luckily, Tyk gateway has &lt;a href=&quot;https://tyk.io/docs/product-stack/tyk-gateway/release-notes/version-4.1/&quot;&gt;recently started supporting&lt;/a&gt; OpenAPI specification as API definition language. But, unfortunately, I haven&apos;t found how to make Tyk gateway serve the merged OAS definition. So, there is a separate process for serving a unified API definition using a standalone Swagger instance. See the deployment diagram above.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I&apos;m not covering the separate Swagger instance in this article but taking a look at &lt;a href=&quot;https://www.npmjs.com/package/openapi-merge-cli&quot;&gt;openapi-merge-cli&lt;/a&gt; might give you an idea how to get a single OAS to serve.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The process of setting up such a solution and the challenges inherent in the process are described below.&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;Let&apos;s consider this example: we store data about the orders placed in our application in one Postgres database while master data of the corporate partners of ours is stored in another. For those two we want to create separate PostgREST API instances. We want to provide centralised access to the both instances of our data API so that from the user standpoint there is no difference where data is coming from.&lt;/p&gt;
&lt;p&gt;When there is a request &lt;code&gt;GET /transactions/orders&lt;/code&gt; coming to API gateway, it should behave like this:&lt;/p&gt;
&lt;p&gt;&lt;span&gt;
      &lt;span&gt;
    &lt;span&gt;&lt;/span&gt;
  &lt;img alt=&quot;Use case 1&quot; src=&quot;https://hopefullysurprising.com/static/e3645581223e61831f0d257207cec2e5/f058b/flow1.png&quot; /&gt;
  &lt;/span&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;and when a request &lt;code&gt;GET /partners/companies&lt;/code&gt; is coming, it should work like that:&lt;/p&gt;
&lt;p&gt;&lt;span&gt;
      &lt;span&gt;
    &lt;span&gt;&lt;/span&gt;
  &lt;img alt=&quot;Use case 2&quot; src=&quot;https://hopefullysurprising.com/static/a20500596543207f582befc34bb030dc/f058b/flow2.png&quot; /&gt;
  &lt;/span&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;We will achieve it by doing the following things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Start two Postgres databases and two PostgREST instances&lt;/li&gt;
&lt;li&gt;Get available OpenAPI specification for the instances&lt;/li&gt;
&lt;li&gt;Convert the provided OpenAPI specifications from version 2 to version 3&lt;/li&gt;
&lt;li&gt;Start Tyk gateway&lt;/li&gt;
&lt;li&gt;Import OpenAPI specifications of version 3 into Tyk gateway&lt;/li&gt;
&lt;li&gt;Check how it works&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Start Postgres databases and PostgREST instances&lt;/h3&gt;
&lt;p&gt;As PostgREST setup is very nicely described in the &lt;a href=&quot;https://postgrest.org/en/stable/tutorials/tut0.html&quot;&gt;official documentation&lt;/a&gt;, I&apos;m not explaining it here in detail. If you want to play with the overall solution on a good example, please, follow the instructions on PostgREST website. Otherwise, feel free to experiment with your OpenAPI compliant server or use &lt;a href=&quot;https://github.com/hopefullysurprising/tyk-postgrest-oas-example&quot;&gt;the sample application I pushed to GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Please note that Postgres databases require some configuration to be PostgREST-ready. Check out &lt;a href=&quot;https://github.com/hopefullysurprising/tyk-postgrest-oas-example/blob/main/postgres1/docker-entrypoint-initdb.d/init.sh&quot;&gt;the initialisation script&lt;/a&gt; if you want to see how the sample application is prepared.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If you are going to bring your own OpenAPI-compliant server, check the version of the specification. You can easily verify the version by checking either &lt;code&gt;swagger&lt;/code&gt; or &lt;code&gt;openapi&lt;/code&gt; value in the root of your OAS definition.&lt;/p&gt;
&lt;p&gt;If you&apos;re on the version 3, feel free jump straight to &quot;Import OAS 3.0 specification&quot; section. Otherwise, follow the conversion steps below.&lt;/p&gt;
&lt;h3&gt;Get available OpenAPI specification for instances&lt;/h3&gt;
&lt;p&gt;PostgREST doesn’t support the static generation of OAS because it learns the schema of the database at the moment of startup. So, if specification needs to be extracted, at least one PostgREST instance should be up and running to introspect the database and return OAS definition.&lt;/p&gt;
&lt;p&gt;While I referred to PostgREST documentation in the first step, here, I want to explicitly mention that by default the OpenAPI schema considers permissions and presents available endpoints only. To get the whole schema, you need &lt;a href=&quot;https://postgrest.org/en/stable/references/configuration.html#openapi-mode&quot;&gt;to change &lt;code&gt;openapi-mode&lt;/code&gt; for the PostgREST instance to &lt;code&gt;ignore-privileges&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you&apos;ve done it right, you should be able to access the schema of PostgREST server at the root path. Detailed information is available in &lt;a href=&quot;https://postgrest.org/en/stable/references/api/openapi.html&quot;&gt;documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;
      &lt;span&gt;
    &lt;span&gt;&lt;/span&gt;
  &lt;img alt=&quot;OAS in a browser&quot; src=&quot;https://hopefullysurprising.com/static/9272ea82ad1929660554bcfa5ebb9017/f058b/oas-text.png&quot; /&gt;
  &lt;/span&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;Convert PostgREST OAS from version 2.0 to 3.0.0&lt;/h3&gt;
&lt;p&gt;As I mentioned in the beginning, PostgREST currently provides OpenAPI specification of version 2 while &lt;a href=&quot;https://tyk.io/docs/product-stack/tyk-gateway/release-notes/version-4.1/#release-highlights&quot;&gt;Tyk supports versions 3.0.x&lt;/a&gt;. It&apos;s not a big problem because OAS 2 can be converted to OAS 3. It can be done with an official &lt;a href=&quot;https://github.com/swagger-api/swagger-converter&quot;&gt;Swagger Converter&lt;/a&gt;. The converter can be started locally in a Docker container but it&apos;s also available online - &lt;a href=&quot;https://converter.swagger.io/&quot;&gt;https://converter.swagger.io/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Outdated OAS version is &lt;a href=&quot;https://github.com/PostgREST/postgrest/issues/932&quot;&gt;a recognized issue&lt;/a&gt; of PostgREST. As of February&apos;24, the work on making PostgREST providing OAS of version 3 is in progress &lt;a href=&quot;https://github.com/PostgREST/postgrest-openapi/&quot;&gt;in a separate repository&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For simplicity, let&apos;s use the online version of the converter.&lt;/p&gt;
&lt;p&gt;What you need to do is to copy OAS 2 specification and use it in &lt;code&gt;[POST] /convert&lt;/code&gt;. If you&apos;re using Swagger interface, just click &quot;Try it out&quot; and paste specification as body of the request.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;
      &lt;span&gt;
    &lt;span&gt;&lt;/span&gt;
  &lt;img alt=&quot;Swagger Converter input&quot; src=&quot;https://hopefullysurprising.com/static/e728a86862b4ee09e2f97ad4014c2171/f058b/swagger-converter-input.png&quot; /&gt;
  &lt;/span&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;If you hit &quot;Execute&quot;, the converter will respond with the specification of version 3.0.1.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;
      &lt;span&gt;
    &lt;span&gt;&lt;/span&gt;
  &lt;img alt=&quot;Swagger Converter output&quot; src=&quot;https://hopefullysurprising.com/static/677174d293fb3fd96941cb9ed368c4bd/f058b/swagger-converter-output.png&quot; /&gt;
  &lt;/span&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;This version of specification is perfectly compatible with Tyk gateway. Convert specifications for both instances and it will be good to import to Tyk.&lt;/p&gt;
&lt;p&gt;Specification examples for version 2.0 and converted to 3.0.1 &lt;a href=&quot;https://github.com/hopefullysurprising/tyk-postgrest-oas-example/tree/main/oas&quot;&gt;can be found in the sample code&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Prepare Tyk gateway&lt;/h3&gt;
&lt;p&gt;The best way to start a local instance of Tyk is using the Docker image. How it&apos;s done is explained &lt;a href=&quot;https://github.com/TykTechnologies/tyk-gateway-docker&quot;&gt;by official examples of Tyk with Docker Compose&lt;/a&gt; but for the purpose of this experiment you might take the trivial Docker Compose setup from the &lt;a href=&quot;https://github.com/hopefullysurprising/tyk-postgrest-oas-example/tree/main&quot;&gt;sample Git repository&lt;/a&gt; that I have prepared.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Please, note that not the entire definition is shown in the snippet! Navigate to the source to see the whole configuration.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For our case, no special setup is needed but ensure that unauthenticated access is not blocked. Auth is not in the scope of this tutorial.&lt;/p&gt;
&lt;h3&gt;Import PostgREST as OAS to Tyk gateway&lt;/h3&gt;
&lt;p&gt;Now, when we have OAS of version 3.0.1, it can be statically imported as API definition to Tyk.&lt;/p&gt;
&lt;p&gt;I want to note that it&apos;s possible to import OAS dynamically &lt;a href=&quot;https://tyk.io/docs/getting-started/using-oas-definitions/import-an-oas-api/&quot;&gt;by calling the dedicated Tyk API endpoint&lt;/a&gt;. But to simplify the deployment process and to be able to version gateway as a whole with Git I do this import statically.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Another possible option might be &lt;a href=&quot;https://tyk.io/docs/tyk-sync/&quot;&gt;Tyk Sync&lt;/a&gt; but it&apos;s in early access now so I&apos;m not considering it for production while it&apos;s not strictly necessary.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We will create the files in the &lt;code&gt;apps&lt;/code&gt; folder and provide this folder as a volume to Tyk gateway container to be available at &lt;code&gt;app_path&lt;/code&gt; from the Tyk config.&lt;/p&gt;
&lt;p&gt;You need to create two &lt;a href=&quot;https://tyk.io/docs/getting-started/key-concepts/what-is-an-api-definition&quot;&gt;Tyk API definitions&lt;/a&gt; per upstream API - one is classic Tyk API definition to store overall API configuration (such as auth, rate limitation and so on) and one for OAS definition with available endpoints.&lt;/p&gt;
&lt;p&gt;For classic Tyk API definition, here is the sample configuration (&lt;code&gt;apps/postgrest1.json&lt;/code&gt;):&lt;/p&gt;

The points of interest here are:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;api_id&lt;/code&gt; must match the one in the respective OAS definition&lt;/li&gt;
&lt;li&gt;&lt;code&gt;is_oas&lt;/code&gt; is set to &lt;code&gt;true&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;proxy.listen_path&lt;/code&gt; and &lt;code&gt;proxy.target_url&lt;/code&gt; specify at what subpath to receive the requests for the upstream API and at what URL to redirect to&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;&lt;code&gt;proxy.target_url&lt;/code&gt; is set as an environment variable. In the sample Docker Compose setup, it&apos;s passed to Tyk gateway as a secret. If you are interested in how it&apos;s done, check out this recipe in &lt;a href=&quot;https://tyk.io/docs/tyk-configuration-reference/kv-store/#3-api-definition---listenpath-and-target-url&quot;&gt;Tyk docs&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For Tyk OAS API definition, &lt;a href=&quot;https://tyk.io/docs/tyk-apis/tyk-gateway-api/oas/x-tyk-oas-doc/&quot;&gt;documentation&lt;/a&gt; is not very clear which is understandable for a feature which is still pretty new. To understand how to work with it, read the following paragraph carefully.&lt;/p&gt;
&lt;p&gt;For the respective OAS part, we need to take the OAS definition of version 3, add additional information for Tyk by placing a configuration object &lt;code&gt;x-tyk-gateway&lt;/code&gt; in the root of OAS JSON and save this file next to classic Tyk API definition. In our case, it&apos;s &lt;code&gt;postgrest1-oas.json&lt;/code&gt;.&lt;/p&gt;

You need to copy some information that you have placed in the classic definition earlier.
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;x-tyk-api-gateway.info.id&lt;/code&gt; should match &lt;code&gt;api_id&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;x-tyk-api-gateway.upstream.url&lt;/code&gt; should match &lt;code&gt;proxy.target_url&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;x-tyk-api-gateway.server.listen_path&lt;/code&gt; should match &lt;code&gt;proxy.listen_path&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The same should be repeated for the second set of API endpoints that are provided by the second PostgREST instance.&lt;/p&gt;
&lt;p&gt;That&apos;s basically it. Congratulations! We can check how it works now.&lt;/p&gt;
&lt;h3&gt;Try it out&lt;/h3&gt;
&lt;p&gt;If you followed the example and used docker-compose setup from my repo, the gateway is running on the port &lt;code&gt;8888&lt;/code&gt;. You might give it a try.&lt;/p&gt;
&lt;div&gt;&lt;pre&gt;&lt;code&gt;curl --request GET --url http://localhost:8888/transactions/orders&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;and&lt;/p&gt;
&lt;div&gt;&lt;pre&gt;&lt;code&gt;curl --request GET --url http://localhost:8888/partners/companies&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;should provide you with data that is saved to the database in the initialisation script.&lt;/p&gt;
&lt;p&gt;If you see some response there, awesome! You&apos;ve made it working. Now, you might do something special with the configuration to get something that fits your needs the most.&lt;/p&gt;
&lt;div&gt;&lt;pre&gt;&lt;code&gt;[
	{
		&quot;createdAt&quot;: &quot;2024-02-11T16:56:07.810486+00:00&quot;,
		&quot;id&quot;: 1,
		&quot;orderNumber&quot;: &quot;1&quot;,
		&quot;status&quot;: &quot;Placed&quot;,
		&quot;updatedAt&quot;: &quot;2024-02-11T16:56:07.810486+00:00&quot;
	},
	{
		&quot;createdAt&quot;: &quot;2024-02-11T16:56:23.915413+00:00&quot;,
		&quot;id&quot;: 2,
		&quot;orderNumber&quot;: &quot;2&quot;,
		&quot;status&quot;: &quot;Delivered&quot;,
		&quot;updatedAt&quot;: &quot;2024-02-11T16:56:23.915413+00:00&quot;
	}
]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;If you have reached this point, I&apos;m really grateful for you. Your choice of technologies for your next project opens endless possibilities for something special.&lt;/p&gt;
&lt;p&gt;With PostgREST, you might create scalable and configurable API access to your database. Just check what you might do with &lt;a href=&quot;https://www.2ndquadrant.com/en/blog/application-users-vs-row-level-security/&quot;&gt;JWT issued by an IdP&lt;/a&gt;. Having RBAC and ABAC for users without any user table - that sounds nice, doesn&apos;t it?&lt;/p&gt;
&lt;p&gt;With Tyk, you have a great opportunity to be future-proof with &lt;a href=&quot;https://tyk.io/blog/enter-the-leader-tyk-recognised-as-a-leader-in-gartners-2023-magic-quadrant-for-api-management/&quot;&gt;a Gartner&apos;s leader solution&lt;/a&gt;. Rate limiting, onboarding new APIs easily, JWT validation before even going to an upstream API. These will be very much welcome in your next API project. And with support of static API definitions and versioning you might be ambitious enough to target multiple nines.&lt;/p&gt;
&lt;p&gt;And if you&apos;re compliant with OpenAPI, developers integrating with your API will appreciate your system. It&apos;s not a straightforward thing merging multiple OASs and serving it as a single Swagger but it&apos;s also not overly challenging. While the result is exciting.&lt;/p&gt;
&lt;p&gt;As usual, it would be awesome to hear about your vision of this design and about what immediate improvements or enhancements you are tempted to try in your version of such a setup.&lt;/p&gt;
&lt;p&gt;Good luck. It will be fun.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Beyond the Scaffold: Evolving Software Projects with Yeoman Generators]]></title><description><![CDATA[Starting anew always brings a lot of excitement. Every new project gives a mix of confidence from making first steps you've trained a…]]></description><link>https://hopefullysurprising.com/yeoman-continuous-scaffolding/</link><guid isPermaLink="false">https://hopefullysurprising.com/yeoman-continuous-scaffolding/</guid><pubDate>Fri, 29 Dec 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;span&gt;
      &lt;span&gt;
    &lt;span&gt;&lt;/span&gt;
  &lt;img alt=&quot;Scaffolding is for different projects&quot; src=&quot;https://hopefullysurprising.com/static/4a7bd29435294f46d0e0eb24a07290c9/f058b/scaffolding.png&quot; /&gt;
  &lt;/span&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Starting anew always brings a lot of excitement. Every new project gives a mix of confidence from making first steps you&apos;ve trained a million times and anticipation that this time the results will be greater than ever before because you&apos;ll try some new answers to old challenges. At every new beginning, you&apos;re stronger than ever before.&lt;/p&gt;
&lt;p&gt;It&apos;s not surprising that software developers like starting new applications, right?&lt;/p&gt;
&lt;p&gt;Nowadays, we create more software packages than ever before. Microservices, microfrontends, microapps, monorepos and tools for them... You need a new module - you isolate it radically, define the communication interface and let it ripen.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Reasonably, the scaffolding tools are more important than ever before.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;They help speed up project creation, ensure it&apos;s aligned with the best practices and swiftly bring you to the point of creating something that truly creates value - new automations.&lt;/p&gt;
&lt;p&gt;Recently, I have been experimenting with extreme modularisation in my Node.js-based project. My objective is to keep it maintainable long term. In respect to it, I need to make each part of the ecosystem genuinely independent - every part should be ready for growth that doesn&apos;t impact other applications. To support creating microapplications in a sustainable manner, I need to ensure one thing - these numerous separate projects will be maintained well.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;How quickly a project is created doesn&apos;t matter much in the long run if maintainability is compromised.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If my scaffolding tool doesn&apos;t help me keep settings consistent between dozens of packages long term, I don&apos;t need such a tool. I need a tool that will help me &quot;make up&quot; a project whenever I need it.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;
      &lt;span&gt;
    &lt;span&gt;&lt;/span&gt;
  &lt;img alt=&quot;Blueprint&quot; src=&quot;https://hopefullysurprising.com/static/7ca4a51853a5f235a88c18bfec1bdfea/f058b/blueprint.png&quot; /&gt;
  &lt;/span&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Vision&lt;/h2&gt;
&lt;p&gt;My goal was to find or create a tool that will not only take the burden of scaffolding a new project quickly and in a consistent manner (configuration, packages, package versions and so on) but that will also be capable of bringing a project up to &lt;em&gt;advancing standards&lt;/em&gt; at any point in future.&lt;/p&gt;
&lt;p&gt;If I use traditional approach. I run the generator that promotes current project standards in an empty folder and I get a project that meets the requirements &lt;em&gt;today&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;But the standards change over time. Let two months pass after the initial project creation, and a new fast TypeScript compiler reaches production readiness, TypeScript itself get a new version, license of choice has changed for your open source projects and a million other things get updated. And you change the standards because you must.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When running the standards ambassador (the scaffolding tool) in the project again, I want my project to get up to the height of the new standards (ideally, without breaking the application).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That would help take care of one of the most important aspects of why we need a tool - ensure consistency of standards between the projects. And do that continuously.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;
      &lt;span&gt;
    &lt;span&gt;&lt;/span&gt;
  &lt;img alt=&quot;Struggles of choice&quot; src=&quot;https://hopefullysurprising.com/static/a767b1140d212613ed9f4ac65a41a265/f058b/cat-plates.png&quot; /&gt;
  &lt;/span&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;h3&gt;Platform&lt;/h3&gt;
&lt;p&gt;While creating a scaffolding engine from scratch might be a good idea, I decided to review ready solutions that have the following capabilities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Generator scripts should be easy to create&lt;/li&gt;
&lt;li&gt;Generator scripts should be easy to compose&lt;/li&gt;
&lt;li&gt;At every step, there should be freedom to decide if and how an atomic change should be done&lt;/li&gt;
&lt;li&gt;The tool should be aware of Node.js context and provide relevant utilities (adding dependencies, for example)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Less important but good to have capabilities are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The tool should have built-in ability to collect user input during the generation stage&lt;/li&gt;
&lt;li&gt;The tool should have ability to work with file templates&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I have reviewed a few solutions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://yeoman.io&quot;&gt;Yeoman&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://plopjs.com&quot;&gt;Plop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.hygen.io&quot;&gt;Hygen&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I have to say the mentioned tools can provide a platform for flexible and composable generators but doing that in Plop and Hygen was quite an effort since the first one sets the generator parameters at the beginning of the execution (statically) and the second one is more template-oriented while I needed to put significant pieces of code in places to control what and how to do. I found it very challenging  to do what I want the way I want with those two tools. Yeoman appeared much more permissive in this regard.&lt;/p&gt;
&lt;p&gt;That&apos;s why I chose Yeoman.&lt;/p&gt;
&lt;p&gt;I have implemented my own generator and published source code on &lt;a href=&quot;https://github.com/hopefullysurprising/generator-code-makeup&quot;&gt;GitHub&lt;/a&gt; and &lt;a href=&quot;https://www.npmjs.com/package/generator-code-makeup&quot;&gt;npmjs&lt;/a&gt;. Below, I&apos;ll cover the main challenges I had when implementing the logic I described above and how I approached them. Also, I&apos;ll mention what else would be great to have in a scaffolding tool like this.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;
      &lt;span&gt;
    &lt;span&gt;&lt;/span&gt;
  &lt;img alt=&quot;Challenges&quot; src=&quot;https://hopefullysurprising.com/static/2b43243233bbb75d9b515e87bea6b2c1/f058b/cat-challenge.png&quot; /&gt;
  &lt;/span&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;Challenges&lt;/h3&gt;
&lt;h4&gt;Composability&lt;/h4&gt;
&lt;p&gt;One of the requirements was composability of generators and I achieved it by introducing two general types of generators:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A &quot;Feature&quot; generator. They execute a set of atomic operations aiming to make up a particular aspect of the project.&lt;/li&gt;
&lt;li&gt;A &quot;Makeup&quot; generator. They execute multiple feature generators in a particular order.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For example, to ensure that there is an npm package initialised and the package name, author and license statements are populated is a &quot;feature&quot; (&lt;a href=&quot;https://github.com/hopefullysurprising/generator-code-makeup/blob/main/src/generators/feature-initiate-npm-package/index.ts&quot;&gt;&lt;code&gt;feature-initiate-npm-package&lt;/code&gt;&lt;/a&gt;). Initialising Jest for unit tests and configuring it (&lt;a href=&quot;https://github.com/hopefullysurprising/generator-code-makeup/blob/main/src/generators/feature-initiate-jest/index.ts&quot;&gt;&lt;code&gt;feature-initiate-jest&lt;/code&gt;&lt;/a&gt;) is also considered a &quot;feature&quot;.&lt;/p&gt;
&lt;p&gt;Running the first feature and the second feature is responsibility of a &quot;makeup&quot; generator.&lt;/p&gt;
&lt;p&gt;The list of &quot;feature&quot; and &quot;makeup&quot; generators and what they do can be found in the repo &lt;a href=&quot;https://github.com/hopefullysurprising/generator-code-makeup&quot;&gt;README&lt;/a&gt;. All of them are supposed to be able to run on their own.&lt;/p&gt;
&lt;h4&gt;Atomic changes&lt;/h4&gt;
&lt;p&gt;To stay flexible about what and how to do, I employed the feature of Yeoman to define the separate changes in separate generator methods that are ran in a particular order (see &lt;a href=&quot;https://yeoman.io/authoring/&quot;&gt;&quot;Adding your own functionality&quot; in their docs&lt;/a&gt;). All changes are small, independent adjustments that collectively contribute to the overall project structure.&lt;/p&gt;
&lt;p&gt;A good example is &lt;a href=&quot;https://github.com/hopefullysurprising/generator-code-makeup/blob/main/src/generators/feature-initiate-typescript/index.ts&quot;&gt;feature-initiate-typescript&lt;/a&gt; generator. For every mini-change, it has a separate safe-to-run-independently method. For example, &lt;code&gt;enableEsModuleInterop&lt;/code&gt; gently processes the options and checks that one particular setting matches the expectations around it (it should be true for having &lt;a href=&quot;https://jestjs.io/docs/ecmascript-modules&quot;&gt;ESM support in Jest&lt;/a&gt;). If the current value doesn&apos;t match, in this and only this case, we change it.&lt;/p&gt;
&lt;p&gt;By creating these atomic independent flexible brush strokes, we can construct the project carefully, having flexibility to do something extra if needed.&lt;/p&gt;
&lt;p&gt;A good example of something extra can be found in another generator - &lt;a href=&quot;https://github.com/hopefullysurprising/generator-code-makeup/blob/main/src/generators/feature-initiate-npm-package/index.ts&quot;&gt;feature-initiate-npm-package&lt;/a&gt;. When creating a LICENSE file, we also check if there is any existing &lt;code&gt;LICENSE.md&lt;/code&gt; or &lt;code&gt;LICENSE.txt&lt;/code&gt; in the folder already. If there is some, an additional cleanup should be done. And with these atomic changes we can do these changes in an optimal way whenever is needed.&lt;/p&gt;
&lt;h4&gt;Profile data&lt;/h4&gt;
&lt;p&gt;My goal is to keep multiple projects generated with the tool consistent. That means, the decisions that have been made before should be taken into consideration at every run of the generator. Let&apos;s consider 3 different situations:&lt;/p&gt;
&lt;h5&gt;1. Setting project name&lt;/h5&gt;
&lt;p&gt;Every project has a name. And it&apos;s one of the first fields you fill in &lt;code&gt;package.json&lt;/code&gt;. When you start a project and fill this field, why would you ever change it using a generator? Once user input is received, it can be stored in the project itself. And at every next run of the generator, if name is already stored in a project file, we can make an assumption - nothing to do here, it&apos;s named already.&lt;/p&gt;
&lt;p&gt;In such cases, &lt;code&gt;package.json&lt;/code&gt; seems to be the most natural place to store such information between the generator runs. So, the project configuration is considered generator configuration in such cases.&lt;/p&gt;
&lt;h5&gt;2. Choosing distribution type&lt;/h5&gt;
&lt;p&gt;Every project makes assumptions about the model of code distribution. Some are open source, some are proprietary and all of them should have right license information filled. But the way of specifying license and terms of use for &lt;em&gt;a category&lt;/em&gt; of projects can change over time. Today, you use &quot;LGPL-3.0&quot; for your open source projects, tomorrow, you might decide that &quot;MIT&quot; is a better choice.&lt;/p&gt;
&lt;p&gt;In such case, you can&apos;t judge by presence of a license file that the project is all set. The project needs remember the distribution type, not what license it meant at the time of running.&lt;/p&gt;
&lt;p&gt;For such things, Yeoman provides &lt;a href=&quot;https://yeoman.io/authoring/storage&quot;&gt;Storage API&lt;/a&gt; that you can use from the code of your generator. Every configuration value is preserved in a special JSON file in the root of your generated project and this configuration can be read from and written to when the generator is running.&lt;/p&gt;
&lt;p&gt;Good job, Yeoman.&lt;/p&gt;
&lt;h5&gt;3. Setting an author of a project&lt;/h5&gt;
&lt;p&gt;Another type of information that you might want to preserve between generator runs can, in fact, be reused between projects. For example, once you set &quot;author.email&quot; for one project, it might be used in other ones. There shouldn&apos;t be a need to ask for it again and again. It should be profile-level configuration.&lt;/p&gt;
&lt;p&gt;Unfortunately, this feature is not well documented in Yeoman (I haven’t found any mention of it in the documentation. I found the hints of it in source code of Yeoman-Generator). Also, this global &lt;code&gt;.yo-rc-global.json&lt;/code&gt; file created in the home directory that contains a profile JSON file saves prompts per-project. It’s not enough for my project, so I have created a separate global config storage. Check out &lt;a href=&quot;https://github.com/hopefullysurprising/generator-code-makeup/blob/main/src/utilities/globalConfigurationUtilities.ts&quot;&gt;file &lt;code&gt;globalConfigurationUtilities.ts&lt;/code&gt;&lt;/a&gt;, if you’re curious to learn more.&lt;/p&gt;
&lt;h5&gt;Three types of configuration storages are enough for now&lt;/h5&gt;
&lt;p&gt;By choosing the right place for storing configuration, I can ensure that necessary information is asked as many times as many times it&apos;s strictly needed. That simplifies repetitive runs of the generator for maintenance purpose. I need it to be as straightforward as possible.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;
      &lt;span&gt;
    &lt;span&gt;&lt;/span&gt;
  &lt;img alt=&quot;Futuristic blueprint&quot; src=&quot;https://hopefullysurprising.com/static/69482425affd970b29fce1d55bdd58f2/f058b/futuristic-blueprint.png&quot; /&gt;
  &lt;/span&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The full code of the generator I was experimenting with can be found on &lt;a href=&quot;https://github.com/hopefullysurprising/generator-code-makeup&quot;&gt;GitHub&lt;/a&gt; and &lt;a href=&quot;https://www.npmjs.com/package/generator-code-makeup&quot;&gt;npmjs&lt;/a&gt;. I&apos;m glad that I have the generator as the dedicated place to store design standards for dozens of microapplications that I create. What is especially important in this very case, it is not a one-time activity but a continuous process of keeping projects standards consistent between my projects over long time.&lt;/p&gt;
&lt;p&gt;Of course, for this particular generator, it&apos;s only beginning. A lot of things need to be considered and addressed in future.&lt;/p&gt;
&lt;p&gt;Project types are different. Frontend is not like backend. A REST API microservice significantly differs from a GraphQL microservice. That will bring another challenge of composing general &quot;feature&quot; generators with very specific &quot;feature&quot; generators. That should be possible to do elegantly but that will impact the generator project structure.&lt;/p&gt;
&lt;p&gt;Also, to be able to use the generator in different contexts (for your personal projects, for projects of your company, for projects you help a friend with and so on), I would like to introduce proper support of profiles that will allow to use varying for-all-projects presets.&lt;/p&gt;
&lt;p&gt;And last but not least, I would be happy to make a generator capable of assisting with possible migrations. For example, a new standard version of a dependency might have breaking changes (some method or argument might be removed) and it&apos;s possible to identify the disaster scope automatically. Is this removed method used in any of the project files? Do updated methods receive the right number of arguments? That should be pretty feasible through leveraging text search and AST tools.&lt;/p&gt;
&lt;p&gt;These are some challenges I foresee to encounter when working on this generator in future.&lt;/p&gt;
&lt;p&gt;I would love to hear about your experiences with scaffolding in multiple projects, your challenges and your solutions. If you have an opinion about the challenges I have mentioned above and especially how they might be approached in an elegant way, do let me know through comments, email messages, GitHub issues and pull requests. The generator is designed to grow - it is using TypeScript for writing the generators and is covered the generators with TypeScript-written Jest tests.&lt;/p&gt;
&lt;p&gt;I&apos;ll post a story about covering the generator with tests shortly. Thanks for reading.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[SonarQube Community Edition: Comprehensive Guide for a Free Personal Setup]]></title><description><![CDATA[When we develop software, we want it to be technically remarkable. It's a common desire to ensure high code quality to benefit both others…]]></description><link>https://hopefullysurprising.com/sonarqube-ce-local-setup/</link><guid isPermaLink="false">https://hopefullysurprising.com/sonarqube-ce-local-setup/</guid><pubDate>Tue, 03 Oct 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When we develop software, we want it to be technically remarkable. It&apos;s a common desire to ensure high code quality to benefit both others and your future self. Static code analysers are essential tools that assess your code against predefined rules without running it.&lt;/p&gt;
&lt;p&gt;While enterprise-grade solutions for that might be enterprise-level expensive, some of the most advanced solutions - such as SonarQube - can be used for free if all the pieces of the stack are put together by yourself.&lt;/p&gt;
&lt;p&gt;In this material, I will walk you through the process of creating personal SonarQube Community Edition instance and setting up the scanner on an example of NodeJS project that I wrote about some time ago (&lt;a href=&quot;https://hopefullysurprising.medium.com/building-a-typescript-compatible-webpack-loader-a-plantuml-mind-map-example-c0b3ec9bc033&quot;&gt;Creating a custom webpack loader for text files, compatible with TypeScript. PlantUML mind map example&lt;/a&gt;). In addition to that, I&apos;ll share a couple of thoughts about why a particular option is chosen and not an alternative.&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;This guide consists of a few parts:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Setting up the stage. Listing system requirements, installing missing dependencies.&lt;/li&gt;
&lt;li&gt;Configuring and starting SonarQube CE instance.&lt;/li&gt;
&lt;li&gt;Creating a SonarQube project, configuring and running the SonarScanner.&lt;/li&gt;
&lt;li&gt;(Bonus). Installing and configuring SonarLint for getting feedback about the code right in IDE.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Setting up the stage&lt;/h3&gt;
&lt;h4&gt;Using Docker as the Runner&lt;/h4&gt;
&lt;p&gt;We will use Docker for running SonarQube server and SonarScanner.&lt;/p&gt;
&lt;p&gt;While it&apos;s possible to run the server and SonarScanner &lt;a href=&quot;https://docs.sonarsource.com/sonarqube/latest/setup-and-upgrade/install-the-server/#installing-sonarqube-from-the-zip-file&quot;&gt;from a binary file&lt;/a&gt;, I choose the Docker way because of two benefits:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It&apos;s much simpler to upgrade SonarQube with Docker: instead of extracting config files, placing binaries somewhere in &lt;code&gt;$PATH&lt;/code&gt; again and adding config back, you simply restart the container from a newer image and bind volumes there.&lt;/li&gt;
&lt;li&gt;You don&apos;t need to have Java in the host machine.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Providing existing database as a storage. Postgres example.&lt;/h4&gt;
&lt;p&gt;SonarQube needs to store a lot of information about the users, rules, projects, scanning sessions, etc. It uses a connected database instance for that.&lt;/p&gt;
&lt;p&gt;SonarQube server &lt;a href=&quot;https://docs.sonarsource.com/sonarqube/latest/setup-and-upgrade/install-the-server/#installing-the-database&quot;&gt;supports different databases&lt;/a&gt; so we can use this advantage. The key for me is to use the database that you&apos;re used to and that is already running in your computer. That helps reduce the footprint of the running SonarQube server - no need to have an entire database instance for a specific use case.&lt;/p&gt;
&lt;p&gt;In my case, it&apos;s Postgres. I&apos;m not sharing the instructions on how to install Postgres itself but here is the &lt;a href=&quot;https://www.postgresql.org/download/linux/ubuntu/&quot;&gt;link to official documentation&lt;/a&gt; about installing Postgres 12 on Ubuntu. SonarQube&apos;s &lt;a href=&quot;https://docs.sonarsource.com/sonarqube/latest/setup-and-upgrade/install-the-server/#example-docker-compose-configuration&quot;&gt;Docker Compose example&lt;/a&gt; refers to PostgreSQL 12 so I&apos;m using this version.&lt;/p&gt;
&lt;p&gt;When the database is available, we need to create schema, database and the user that SonarQube server will be using for storing data. Below is the Postgres way:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: Don&apos;t forget to replace &lt;code&gt;SONAR_DB_PASSWORD&lt;/code&gt; with the real one you&apos;ll be using.&lt;/p&gt;
&lt;div&gt;&lt;pre&gt;&lt;code&gt;CREATE SCHEMA sonarqube;
CREATE USER sonarqube WITH PASSWORD &apos;SONAR_DB_PASSWORD&apos;;
GRANT USAGE ON SCHEMA sonarqube TO sonarqube;
GRANT CREATE ON SCHEMA sonarqube TO sonarqube;
ALTER DEFAULT PRIVILEGES IN SCHEMA sonarqube GRANT ALL ON TABLES TO sonarqube;
ALTER DEFAULT PRIVILEGES IN SCHEMA sonarqube GRANT ALL ON SEQUENCES TO sonarqube;
ALTER DEFAULT PRIVILEGES IN SCHEMA sonarqube GRANT ALL ON FUNCTIONS TO sonarqube;
CREATE DATABASE sonarqube;
ALTER DATABASE sonarqube OWNER TO sonarqube;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Configuring and starting SonarQube CE instance&lt;/h3&gt;
&lt;h4&gt;SonarQube server&lt;/h4&gt;
&lt;p&gt;Now, when the database and Docker engine are available in the host machine, we can start the SonarQube server container.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Some troubleshooting notes will follow the happy path of the installation.&lt;/em&gt;&lt;/p&gt;
&lt;h5&gt;Create the Docker volumes&lt;/h5&gt;
&lt;div&gt;&lt;pre&gt;&lt;code&gt;sudo docker volume create --name sonarqube_data
sudo docker volume create --name sonarqube_logs
sudo docker volume create --name sonarqube_extensions&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h5&gt;Start the SonarQube server container&lt;/h5&gt;
&lt;div&gt;&lt;pre&gt;&lt;code&gt;sudo docker run --detach --name sonarqube \
	--add-host=host.docker.internal:host-gateway \
	--publish 9000:9000 \
	--env SONAR_JDBC_URL=jdbc:postgresql://host.docker.internal:5432/sonarqube \
	--env SONAR_JDBC_USERNAME=sonarqube \
	--env SONAR_JDBC_PASSWORD=SONAR_DB_PASSWORD \
	--volume sonarqube_data:/opt/sonarqube/data \
	--volume sonarqube_extensions:/opt/sonarqube/extensions \
	--volume sonarqube_logs:/opt/sonarqube/logs \
	sonarqube:community&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;NOTE 1&lt;/strong&gt;: Don&apos;t forget to put the real &lt;code&gt;SONAR_DB_PASSWORD&lt;/code&gt; password from the database created earlier.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NOTE 2&lt;/strong&gt;: If you use different database, put your specific &lt;code&gt;SONAR_JDBC_URL&lt;/code&gt;.&lt;/p&gt;
&lt;h5&gt;Common issues&lt;/h5&gt;
&lt;p&gt;If the Docker container is not starting, &lt;a href=&quot;https://docs.docker.com/engine/reference/commandline/logs/&quot;&gt;check the logs&lt;/a&gt;. The issues I encountered are listed below:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If Elasticsearch complains about the heap size in Linux, update this setting: &lt;code&gt;vm.max_map_count&lt;/code&gt; to allow using more memory.&lt;/li&gt;
&lt;li&gt;If the Postgres DB is not reachable, you might need to update &lt;code&gt;pg_hba.conf&lt;/code&gt; to allow incoming connections for &lt;code&gt;sonarqube&lt;/code&gt; user.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Access SonarQube UI&lt;/h4&gt;
&lt;p&gt;When the container with SonarQube server is running and is available on the port &lt;code&gt;9000&lt;/code&gt; in your machine (see &lt;code&gt;--publish&lt;/code&gt; parameter), you can access &lt;code&gt;https://localhost:9000&lt;/code&gt; in your browser. You&apos;ll be invited to log in. The most secure in the world &lt;code&gt;admin&lt;/code&gt; - &lt;code&gt;admin&lt;/code&gt; are your credentials. You&apos;ll need to change it as soon as you log in for the first time.&lt;/p&gt;
&lt;h3&gt;Running SonarScanner&lt;/h3&gt;
&lt;h4&gt;Generate token for the scanner&lt;/h4&gt;
&lt;p&gt;Before creating and scanning projects, we need to generate a token that will allow the scanner to upload the results to SonarQube server. For a simple setup, we will create a &lt;strong&gt;global token&lt;/strong&gt;. You can do that via opening the user dropdown in the top right corner of the screen and going to &quot;My Account&quot; -&amp;gt; &quot;Security&quot;.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;
      &lt;span&gt;
    &lt;span&gt;&lt;/span&gt;
  &lt;img alt=&quot;Create a global token&quot; src=&quot;https://hopefullysurprising.com/static/d24abf5fb6de0c866d04687c00916650/f058b/0-global-token.png&quot; /&gt;
  &lt;/span&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Other options are explained &lt;a href=&quot;https://docs.sonarsource.com/sonarqube/latest/user-guide/user-account/generating-and-using-tokens/#generating-a-token&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Copy the token and save it in a secure place. &lt;strong&gt;You won&apos;t be able to see it in the UI ever again.&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;Create and configure a SonarQube project&lt;/h4&gt;
&lt;p&gt;In SonarQube dashboard, select &quot;Create Project&quot; and choose &quot;Manually&quot;. Input display name and the project key, choose the default branch.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;
      &lt;span&gt;
    &lt;span&gt;&lt;/span&gt;
  &lt;img alt=&quot;Create a project&quot; src=&quot;https://hopefullysurprising.com/static/80254f3ef198b6eeed581c77664d91b5/f058b/1-create-a-project.png&quot; /&gt;
  &lt;/span&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: For this setup, we will disable versioning so the branch name here doesn&apos;t really matter.&lt;/p&gt;
&lt;p&gt;For the baseline, you can choose the global settings which is the default SonarQube preset.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;
      &lt;span&gt;
    &lt;span&gt;&lt;/span&gt;
  &lt;img alt=&quot;Choose global settings&quot; src=&quot;https://hopefullysurprising.com/static/e2b6e1c1d816e57f93195732c7e96862/f058b/2-set-global-settings.png&quot; /&gt;
  &lt;/span&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;In the opened project page, choose &quot;Locally&quot; for scanning setup.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;
      &lt;span&gt;
    &lt;span&gt;&lt;/span&gt;
  &lt;img alt=&quot;Choose Locally&quot; src=&quot;https://hopefullysurprising.com/static/3a8a6565182b44d200546fe94f8a62cd/f058b/3-scan-locally.png&quot; /&gt;
  &lt;/span&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Provide the global token you generated before and click Next.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;
      &lt;span&gt;
    &lt;span&gt;&lt;/span&gt;
  &lt;img alt=&quot;Select existing token&quot; src=&quot;https://hopefullysurprising.com/static/36485814d22cef306ae1df768018fb86/f058b/4-select-existing-token.png&quot; /&gt;
  &lt;/span&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;You will be given the CLI command to run but you can ignore it. We will be running the scanner in a Docker container. Also, we will provide the parameters via &lt;code&gt;.properties&lt;/code&gt; file and not CLI arguments.&lt;/p&gt;
&lt;p&gt;You can leave the browser page open, it will refresh automatically when the results of scanning were pushed to SonarQube server.&lt;/p&gt;
&lt;p&gt;In the root of the project you&apos;re going to scan with SonarQube, create &lt;code&gt;sonar-project.properties&lt;/code&gt; file with the following content:&lt;/p&gt;
&lt;div&gt;&lt;pre&gt;&lt;code&gt;sonar.projectKey=plantuml-mindmap-loader

sonar.sources=.
sonar.sourceEncoding=UTF-8
sonar.scm.disabled=true
sonar.exclusions=**/node_modules/**,**/coverage/**,**/lib/**
sonar.javascript.lcov.reportPaths=./coverage/lcov.info&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: This file contains instructions on unit-test coverage because this project has Jest tests. If you don&apos;t have it in the project, exclude &lt;code&gt;sonar.javascript.lcov.reportPaths&lt;/code&gt; parameter.&lt;/p&gt;
&lt;h4&gt;Run the scanner&lt;/h4&gt;
&lt;p&gt;As mentioned above, we will run the scanner in a Docker container. You need to execute the below command with a correct &lt;code&gt;SONAR_GLOBAL_TOKEN&lt;/code&gt; and the path to the folder containing the code you scan (this folder should contain &lt;code&gt;sonar-project.properties&lt;/code&gt; you created before).&lt;/p&gt;
&lt;div&gt;&lt;pre&gt;&lt;code&gt;docker run \
	--rm \
	-e SONAR_HOST_URL=&quot;http://localhost:9000&quot; \
	-e SONAR_SCANNER_OPTS=&quot;-Dproject.settings=sonar-project.properties&quot; \
	-e SONAR_TOKEN=&quot;SONAR_GLOBAL_TOKEN&quot; \
	-v &quot;./software/packages/plantuml-mindmap-loader:/usr/src&quot; \
	sonarsource/sonar-scanner-cli:5&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It will take a couple of minutes to execute and after it&apos;s completed you can enjoy the code quality assessment results in SonarQube&lt;/p&gt;
&lt;p&gt;&lt;span&gt;
      &lt;span&gt;
    &lt;span&gt;&lt;/span&gt;
  &lt;img alt=&quot;Enjoy the results&quot; src=&quot;https://hopefullysurprising.com/static/a1644dcc397b654c8af40c77ae07a7dd/f058b/5-scan-results.png&quot; /&gt;
  &lt;/span&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Review the issues, check suggested solutions, apply a patch and run the scan again. The setup is complete.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;
      &lt;span&gt;
    &lt;span&gt;&lt;/span&gt;
  &lt;img alt=&quot;See issue list&quot; src=&quot;https://hopefullysurprising.com/static/1f5c451b216d5a21ecc60b81172089c2/f058b/6-see-issue-list.png&quot; /&gt;
  &lt;/span&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;
      &lt;span&gt;
    &lt;span&gt;&lt;/span&gt;
  &lt;img alt=&quot;See issue details&quot; src=&quot;https://hopefullysurprising.com/static/6d55eb7e6f69ec9185086e2d9d2fa072/f058b/7-see-issue-details.png&quot; /&gt;
  &lt;/span&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h3&gt;Bonus. Setup SonarLint in VS Code&lt;/h3&gt;
&lt;p&gt;While seeing the results of scanning in SonarQube is super helpful, in software development, shortening the feedback loop for code changes is essential for efficient workflow. For that, there is &lt;a href=&quot;https://github.com/SonarSource/sonarlint-vscode&quot;&gt;SonarLint&lt;/a&gt; extension available for quite a few modern IDEs, including VS Code.&lt;/p&gt;
&lt;p&gt;What makes this extension especially impressive is &lt;a href=&quot;https://docs.sonarsource.com/sonarqube/latest/user-guide/sonarlint-connected-mode/&quot;&gt;the connected mode&lt;/a&gt; when the rules applied to the project are synchronised with SonarQube server.&lt;/p&gt;
&lt;p&gt;To install and configure it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Find and install &lt;a href=&quot;https://github.com/SonarSource/sonarlint-vscode&quot;&gt;SonarLint extension&lt;/a&gt; in VS Code.&lt;/li&gt;
&lt;li&gt;Go to the &quot;SonarLint&quot; tab and click at the plus button in &quot;Connected Mode&quot; section.&lt;/li&gt;
&lt;li&gt;Add details: Server URL (&lt;code&gt;http://localhost:9000&lt;/code&gt;), User Token (can use &lt;code&gt;SONAR_GLOBAL_TOKEN&lt;/code&gt; from before).
&lt;span&gt;
      &lt;span&gt;
    &lt;span&gt;&lt;/span&gt;
  &lt;img alt=&quot;Setup SonarLint&quot; src=&quot;https://hopefullysurprising.com/static/9ea938dd94fec89b294c11916c0ccc42/f058b/8-setup-sonarlint.png&quot; /&gt;
  &lt;/span&gt;
    &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Click &quot;Save Connection&quot;.&lt;/li&gt;
&lt;li&gt;For this connection, click the plus button for &quot;Add project binding&quot;.&lt;/li&gt;
&lt;li&gt;Select the workspace.&lt;/li&gt;
&lt;li&gt;Select the project created in SonarQube server.&lt;/li&gt;
&lt;li&gt;Enjoy the binding.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can see the syntax highlight right in your IDE now and see the underlines going away as you address the quality concerns.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;
      &lt;span&gt;
    &lt;span&gt;&lt;/span&gt;
  &lt;img alt=&quot;See suggestions&quot; src=&quot;https://hopefullysurprising.com/static/e39034c633a902634750854630b0b443/f058b/9-see-suggestions.png&quot; /&gt;
  &lt;/span&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Results &amp;amp; Beyond&lt;/h2&gt;
&lt;p&gt;With this SonarQube Community Edition setup, you will be able to ensure outstanding quality in your projects with very little time and resource investments. Variety of benefits attracts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If your project evolves quickly, you can enjoy sturdier stance of your project against changes. Your project will be harder to break, easier to maintain, more challenging to hack.&lt;/li&gt;
&lt;li&gt;If it&apos;s a weekend project, future self will be extremely happy to start a new feature from a reliable checkpoint - with cleaner code, fewer bugs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This guide focuses on the most straightforward setup possible and many possible improvements are not included in the scope not to lose focus on the MVP. A few evident things you might work out yourself:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Integrate SonarQube with CI/CD to ensure the quality gate fully protects your shared branches from code of low quality.&lt;/li&gt;
&lt;li&gt;Create an automated local runner for SonarQube scanner to have a quality gate not before the code is merge but before the code is committed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I hope the length of this guide doesn&apos;t intimidate you from trying this local setup. In fact, it&apos;s pretty straightforward and even lightweight. My objective was to provide information on the aspects of particular steps so that if in your situation a slightly different approach is more suitable, you make this turn and return to the main track with the solution that fits your needs the best.&lt;/p&gt;
&lt;p&gt;And last but not the list, getting quality suggestions from SonarQube is a whole load of fun. You&apos;ll enjoy it.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Our Experiment with AI-Powered Dev Tools: The GitHub Copilot Experience of One Company]]></title><description><![CDATA[Introduction As someone who constantly searches for ways to improve personal coding performance and performance of my development teams, I…]]></description><link>https://hopefullysurprising.com/github-copilot-pilot-project/</link><guid isPermaLink="false">https://hopefullysurprising.com/github-copilot-pilot-project/</guid><pubDate>Wed, 27 Sep 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;As someone who constantly searches for ways to improve personal coding performance and performance of my development teams, I have been experimenting a lot with AI driven utilities that might assist developers in writing code. &lt;a href=&quot;https://www.tabnine.com&quot;&gt;TabNine&lt;/a&gt;, &lt;a href=&quot;https://openai.com/blog/chatgpt&quot;&gt;ChatGPT 3 &amp;amp; 4&lt;/a&gt; and, finally, &lt;a href=&quot;https://copilot.github.com&quot;&gt;GitHub Copilot&lt;/a&gt; that wraps a special version of OpenAI&apos;s &lt;a href=&quot;https://openai.com/blog/openai-codex&quot;&gt;CodeX&lt;/a&gt; in a tool that is easy-to-integrate with modern IDEs.&lt;/p&gt;
&lt;p&gt;All these tools have proven useful in many situations and allowed me to create utilities for automating daily routines with ease I had never experienced before. My initial intention was to provide all the developers in my teams with access to this tool. But to better justify this regular spending to my company, I decided to conduct a little experiment: a pilot group had been given paid access to GitHub Copilot with a duty to share feedback regularly.&lt;/p&gt;
&lt;p&gt;I have to say that it was a very right decision because the results weren&apos;t what I expected to see. And, sorry for the spoiler, we changed our plans - we&apos;re not giving it to everyone.&lt;/p&gt;
&lt;h2&gt;Before we started: Security&lt;/h2&gt;
&lt;p&gt;Like any company that considers information security a paramount subject, we reviewed the terms of usage of GitHub Copilot with our legal team. We ensured that running GitHub Copilot in a client machine doesn&apos;t involve long-term data collection by Microsoft. The prompts are discarded immediately after providing (see &lt;a href=&quot;https://docs.github.com/en/site-policy/privacy-policies/github-copilot-for-business-privacy-statement#prompts-1&quot;&gt;details&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;We didn&apos;t include &lt;a href=&quot;https://docs.github.com/en/copilot/github-copilot-chat/using-github-copilot-chat&quot;&gt;Copilot Chat&lt;/a&gt; in the scope of the pilot project (that is in beta at the moment of writing). Using the Chat might require accepting more conditions for data collection and processing.&lt;/p&gt;
&lt;h2&gt;Setup&lt;/h2&gt;
&lt;p&gt;For the pilot group, we have selected 5 software developers of different experience (from 3 years in IT to 10+ years) and different skillset (React.js in the frontend, Ruby on Rails and Node.js in the backend, GitLab CI for CI/CD pipelines). These 5 people were given GitHub Copilot licenses for 10 weeks and were asked to provide feedback every 2 weeks.&lt;/p&gt;
&lt;p&gt;The common themes in the feedback have been collected by me (I also was a part of this program. I wanted to stop paying for it from my own wallet) and are presented in this article.&lt;/p&gt;
&lt;h2&gt;Observations&lt;/h2&gt;
&lt;h3&gt;It&apos;s easy to start using it&lt;/h3&gt;
&lt;p&gt;What everyone has noticed and mentioned is how easy it is to install in VS Code. Basically, after 2 minutes of installing and configuring GitHub Copilot, the tool is ready for use and provides suggestions in all the files you work in. Convenience of installing is extreme.&lt;/p&gt;
&lt;p&gt;But I have to mention that some people found it easier to migrate from Sublime to VS Code to start using it. Technically, it should be possible with Sublime though (this &lt;a href=&quot;https://github.com/TerminalFi/LSP-copilot&quot;&gt;one&lt;/a&gt; looks interesting but we didn&apos;t spend time on it).&lt;/p&gt;
&lt;h3&gt;When it&apos;s wrong, you are (rightfully) not happy&lt;/h3&gt;
&lt;p&gt;One of the things noticed by more than one person was the effect of a wrong suggestion. When the suggestion is incorrect, you lose time. At best, you gave a second to Copilot to provide you with something you don&apos;t want to see. At worst, you have your code broken and your page turns red. And absolutely always, you have a half-a-second pause to see what it has got for you. You wait. For a fraction of a second but you wait. These micro-pauses are not what we liked.&lt;/p&gt;
&lt;h3&gt;It might be helpful (and dangerous) for beginners&lt;/h3&gt;
&lt;p&gt;A couple of people have mentioned that they think GitHub Copilot might be more useful to beginners in technologies. While I partially agree with that, I also have concerns. Any complicated tool requires a skilful operator. The suggestions from AI-powered tools must be validated by a person who knows what the code does and what it should do. Without this validation, it might be like an untamed &lt;a href=&quot;https://en.wikipedia.org/wiki/Id,_ego_and_super-ego&quot;&gt;id without ego&lt;/a&gt;. Who might want more chaos in their project?&lt;/p&gt;
&lt;h3&gt;Tricky things (sometimes) don&apos;t get less tricky with Copilot&lt;/h3&gt;
&lt;p&gt;One of more experienced developers tried to use Copilot for tricky things he is not extremely familiar with. The example is writing PostgreSQL query that filters records based on nested parameters stored in JSONB value. The suggestions given by Copilot were always somewhat imperfect. A solution was found with help of good old ChatGPT after specifying context explicitly (schema, version of PostgreSQL, version of ORM and so on).&lt;/p&gt;
&lt;h3&gt;It might document your function better than you&lt;/h3&gt;
&lt;p&gt;We spotted that at times GitHub Copilot helps a lot with documenting a method or a class. It&apos;s not lazy, it will write what&apos;s needed to full extent. It considers the context of the project, all the implications of underlying logic and it produces a good short summary. Probably, you don&apos;t want all of your methods to be documented in plain English. Or do you?&lt;/p&gt;
&lt;h3&gt;Short takes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Sometimes it surprises you with how much you&apos;re guessable.&lt;/li&gt;
&lt;li&gt;If you have a typo in your code, it inherits it and can suggest code with this typo again.&lt;/li&gt;
&lt;li&gt;Try start typing &lt;code&gt;Database tables walk into a bar&lt;/code&gt; for a bit of fun.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Summary &amp;amp; Decision&lt;/h2&gt;
&lt;p&gt;The results are quite surprising and that&apos;s why it&apos;s in this blog. Every developer in our experiment came to a simple conclusion: if the company doesn&apos;t provide the license, they wouldn&apos;t purchase it themselves. But that statement might be a bit too generalised - I personally know a few people who pay for this tool for personal use. Including myself. The drawbacks of having wrong suggestions are usually overweighed by ability to create an Apple Script or getting a suggestion on Jira API protocol without leaving IDE.&lt;/p&gt;
&lt;p&gt;Copilot Chat seems to be a more promising feature. A conversation-style tool (like ChatGPT) allows adding context that you realise is lacking. Also, it provides a much more natural environment of chatting with an extremely experienced developer who knows programming inside out but probably unaware of what exactly you&apos;re working on right now.&lt;/p&gt;
&lt;p&gt;We decided to communicate to the developers that they can request a license for themselves. They will need to write a form, to justify the purchase. In simpler terms, we let developers request a tool from the company, just like a specific IDE or GUI for a database. This approach will help us optimise our software expenses while ensuring that those who genuinely want and need the tool have access to it.&lt;/p&gt;
&lt;p&gt;GitHub Copilot is not a one-size-fits-all solution, and it was never intended to be.&lt;/p&gt;
&lt;p&gt;Searching for the next one. Stay tuned.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Building a TypeScript-Compatible Webpack Loader: A PlantUML Mind Map Example]]></title><description><![CDATA[Introduction In the historical book "The Pragmatic Programmer", there has always been an entire chapter dedicated to the importance of the…]]></description><link>https://hopefullysurprising.com/custom-webpack-loader-plantuml-mindmap/</link><guid isPermaLink="false">https://hopefullysurprising.com/custom-webpack-loader-plantuml-mindmap/</guid><pubDate>Wed, 13 Sep 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;In the historical book &lt;a href=&quot;https://pragprog.com/titles/tpp20/the-pragmatic-programmer-20th-anniversary-edition/&quot;&gt;&quot;The Pragmatic Programmer&quot;&lt;/a&gt;, there has always been an entire chapter dedicated to the importance of the ability to process text files of various formats quickly and efficiently (see &quot;The Basic Tools: Text Manipulation&quot;). The ability to use different text formats in various situations has always been essential and likely will continue to be.&lt;/p&gt;
&lt;p&gt;For example, while JSON notation is super useful in many contexts, in some use cases, such as creating mind maps, another format might be much better. For example, &lt;a href=&quot;https://plantuml.com/mindmap-diagram&quot;&gt;PlantUML&apos;s mind maps&lt;/a&gt; might be a more preferable option for storing &lt;a href=&quot;https://en.wikipedia.org/wiki/Serialization&quot;&gt;serialised&lt;/a&gt; trees.&lt;/p&gt;
&lt;p&gt;That&apos;s what we will implement - store this tree-structured data as text in PlanUML format and use it in a JS app.&lt;/p&gt;
&lt;h2&gt;Quick links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Custom webpack loader in &lt;a href=&quot;https://www.npmjs.com/package/plantuml-mindmap-loader&quot;&gt;npm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Source code in &lt;a href=&quot;https://github.com/hopefullysurprising/plantuml-mindmap-loader&quot;&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Vision&lt;/h2&gt;
&lt;p&gt;Although parsing files at runtime might be a good idea, another option might be even more beneficial. If we can integrate loading with the building process, we will be able to ensure that the data required for running the app is a part of the bundle and doesn&apos;t require passing non-common extension files separately.&lt;/p&gt;
&lt;p&gt;In this tutorial, we will create a &lt;a href=&quot;https://webpack.js.org/loaders/&quot;&gt;webpack loader&lt;/a&gt; that will be taking care of parsing PlantUML mind map file, converting the content to a standard JS object and providing the object to any JS module that imports it by file name.&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;We need to do 2 things to achieve the goal: create a loader as an injectable package and integrate this loader with the building process of the project that will be actually importing files with &lt;code&gt;.puml&lt;/code&gt; extension.&lt;/p&gt;
&lt;p&gt;As a bonus, we will setup basic TypeScript support that will allow importing the text file content with type definitions provided.&lt;/p&gt;
&lt;h3&gt;Create a custom loader&lt;/h3&gt;
&lt;h4&gt;Project setup&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;Create a folder:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;mkdir&lt;/span&gt; plantuml-mindmap-loader &lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span&gt;cd&lt;/span&gt; plantuml-mindmap-loader&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Initialise an npm project (you don&apos;t need to change default suggestions as some will be manually updated later in &lt;code&gt;package.json&lt;/code&gt;):&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;npm&lt;/span&gt; init&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Install dev dependencies:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;npm&lt;/span&gt; &lt;span&gt;install&lt;/span&gt; --save-dev @types/webpack typescript&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Add webpack as a peer dependency to the project:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In &lt;code&gt;package.json&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;{&lt;/span&gt;
	...
	&lt;span&gt;&quot;peerDependencies&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
		&lt;span&gt;&quot;webpack&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;&quot;^4.0.0 || ^5.0.0&quot;&lt;/span&gt;
	&lt;span&gt;}&lt;/span&gt;
	...
&lt;span&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; It&apos;s a webpack loader, hence, webpack is always installed in a &quot;parent&quot; project.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Update &lt;code&gt;main&lt;/code&gt; and &lt;code&gt;types&lt;/code&gt; properties in &lt;code&gt;package.json&lt;/code&gt; to paths that we will be using for the results of building:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;{&lt;/span&gt;
	...
	&lt;span&gt;&quot;main&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;&quot;lib/index.js&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
	&lt;span&gt;&quot;types&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;&quot;index.d.ts&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
	...
&lt;span&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Create &lt;code&gt;tsconfig.json&lt;/code&gt; in the root directory of the project with following content:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;{&lt;/span&gt;
	&lt;span&gt;&quot;compilerOptions&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
		&lt;span&gt;&quot;target&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;&quot;es2016&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
		&lt;span&gt;&quot;module&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;&quot;commonjs&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
		&lt;span&gt;&quot;allowJs&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
		&lt;span&gt;&quot;outDir&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;&quot;./lib&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
		&lt;span&gt;&quot;esModuleInterop&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
		&lt;span&gt;&quot;strict&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;true&lt;/span&gt;
	&lt;span&gt;}&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: If you want to use Jest for testing, consider adding the below config to skip building test files, like this:&lt;/p&gt;
&lt;div&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;{&lt;/span&gt;
	...
	&lt;span&gt;&quot;include&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;[&lt;/span&gt;
		&lt;span&gt;&quot;**/*&quot;&lt;/span&gt;
	&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
	&lt;span&gt;&quot;exclude&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;[&lt;/span&gt;
		&lt;span&gt;&quot;**/*.spec.ts&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
		&lt;span&gt;&quot;coverage/**/*&quot;&lt;/span&gt;
	&lt;span&gt;]&lt;/span&gt;
	...
&lt;span&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Create the entry point for the loader - &lt;code&gt;index.ts&lt;/code&gt; - with the following content:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;import&lt;/span&gt; &lt;span&gt;{&lt;/span&gt; validate &lt;span&gt;}&lt;/span&gt; &lt;span&gt;from&lt;/span&gt; &lt;span&gt;&apos;schema-utils&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;
&lt;span&gt;import&lt;/span&gt; &lt;span&gt;{&lt;/span&gt; LoaderContext &lt;span&gt;}&lt;/span&gt; &lt;span&gt;from&lt;/span&gt; &lt;span&gt;&apos;webpack&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;

&lt;span&gt;import&lt;/span&gt; &lt;span&gt;{&lt;/span&gt; parseMindMap &lt;span&gt;}&lt;/span&gt; &lt;span&gt;from&lt;/span&gt; &lt;span&gt;&apos;./src/parseMindMap&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;

&lt;span&gt;interface&lt;/span&gt; &lt;span&gt;LoaderProperties&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
	&lt;span&gt;test&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; string&lt;span&gt;;&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;

&lt;span&gt;const&lt;/span&gt; schema &lt;span&gt;=&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
	&lt;span&gt;type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;&apos;object&apos;&lt;/span&gt; &lt;span&gt;as&lt;/span&gt; &lt;span&gt;&apos;object&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;// `as` is for making the type match `JSONSchema7`&lt;/span&gt;
	&lt;span&gt;properties&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
		&lt;span&gt;test&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
			&lt;span&gt;type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;&apos;string&apos;&lt;/span&gt; &lt;span&gt;as&lt;/span&gt; &lt;span&gt;&apos;string&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;// `as` is for making the type match `JSONSchema7`&lt;/span&gt;
		&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
	&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;

&lt;span&gt;// Should match the definition in index.d.ts&lt;/span&gt;
&lt;span&gt;export&lt;/span&gt; &lt;span&gt;interface&lt;/span&gt; &lt;span&gt;PlantUMLMindMapNode&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
	&lt;span&gt;id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; number&lt;span&gt;;&lt;/span&gt; &lt;span&gt;// Order number of the line in the file&lt;/span&gt;
	&lt;span&gt;label&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; string&lt;span&gt;;&lt;/span&gt;
	&lt;span&gt;tags&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; string&lt;span&gt;[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;
	&lt;span&gt;children&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; PlantUMLMindMapNode&lt;span&gt;[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;

&lt;span&gt;export&lt;/span&gt; &lt;span&gt;default&lt;/span&gt; &lt;span&gt;function&lt;/span&gt; &lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; LoaderContext&lt;span&gt;&amp;lt;&lt;/span&gt;LoaderProperties&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;source&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; string&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; string &lt;span&gt;{&lt;/span&gt;

	&lt;span&gt;// 1. Get options that can be provided to the loader&lt;/span&gt;
	&lt;span&gt;// via webpack loader configuration.&lt;/span&gt;
	&lt;span&gt;// Also, validate these options.&lt;/span&gt;
	&lt;span&gt;const&lt;/span&gt; options &lt;span&gt;=&lt;/span&gt; &lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getOptions&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;
	&lt;span&gt;validate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;schema&lt;span&gt;,&lt;/span&gt; options&lt;span&gt;,&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
		&lt;span&gt;name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;&apos;plantuml-mindmap-loader&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
		&lt;span&gt;baseDataPath&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;&apos;options&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
	&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;

	&lt;span&gt;// 2. Do the core thing of your loader.&lt;/span&gt;
	&lt;span&gt;// In our case, we take `source` which is the content of a file being imported&lt;/span&gt;
	&lt;span&gt;// and convert it to a JS object, recreating the tree structure&lt;/span&gt;
	&lt;span&gt;// defined by PlantUML mind map.&lt;/span&gt;
	&lt;span&gt;const&lt;/span&gt; result &lt;span&gt;=&lt;/span&gt; &lt;span&gt;parseMindMap&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;source&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;

	&lt;span&gt;// 3. We return a string that is a valid JS code&lt;/span&gt;
	&lt;span&gt;// that will be injected as a result of file loading.&lt;/span&gt;
	&lt;span&gt;// In our case, we simply stringify the JSON object.&lt;/span&gt;
	&lt;span&gt;return&lt;/span&gt; &lt;span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;export default &lt;/span&gt;&lt;span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;result&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The key thing is the content of the default exported function. Here, we have 3 building blocks. Take a look at the comments in the code for information.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The functionality of converting PlantUML mind map string should be placed in &lt;code&gt;./src/parseMindMap.ts&lt;/code&gt;. Since, it&apos;s not the core subject of this article, let me only share the &lt;a href=&quot;https://github.com/hopefullysurprising/plantuml-mindmap-loader/blob/main/src/parseMindMap.ts&quot;&gt;link to public GitHub repository&lt;/a&gt; where actual content can be found.&lt;/li&gt;
&lt;li&gt;Create types for this plugin in &lt;code&gt;index.d.ts&lt;/code&gt; so that the consumers of the loader can use it:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;pre&gt;&lt;code&gt;declare module &lt;span&gt;&apos;*.mindmap.puml&apos;&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;

	&lt;span&gt;// Should match the definition in index.ts&lt;/span&gt;
	&lt;span&gt;export&lt;/span&gt; &lt;span&gt;interface&lt;/span&gt; &lt;span&gt;PlantUMLMindMapNode&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
		&lt;span&gt;id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; number&lt;span&gt;;&lt;/span&gt; &lt;span&gt;// Number of the line in the file&lt;/span&gt;
		&lt;span&gt;label&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; string&lt;span&gt;;&lt;/span&gt;
		&lt;span&gt;tags&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; string&lt;span&gt;[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;
		&lt;span&gt;children&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; PlantUMLMindMapNode&lt;span&gt;[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;
	&lt;span&gt;}&lt;/span&gt;
	&lt;span&gt;const&lt;/span&gt; &lt;span&gt;content&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; PlantUMLMindMapNode&lt;span&gt;;&lt;/span&gt;
	&lt;span&gt;export&lt;/span&gt; &lt;span&gt;default&lt;/span&gt; content&lt;span&gt;;&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That should be enough for now. If you&apos;re interested in how it might be covered with unit-tests, take a look at the &lt;a href=&quot;https://github.com/hopefullysurprising/plantuml-mindmap-loader&quot;&gt;GitHub repo&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Integrate it with your target project&lt;/h3&gt;
&lt;p&gt;This part is pretty straightforward. All you need to do is a few things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Install the plugin as a dev dependency (it&apos;s needed for building only)&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;npm&lt;/span&gt; &lt;span&gt;install&lt;/span&gt; --save-dev &lt;span&gt;..&lt;/span&gt;/packages/plantuml-mindmap-loader&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This installation is from a local directory. For installing from npm or from another repository, change the package path.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Update &lt;code&gt;webpack.config.js&lt;/code&gt; to include this loader:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;pre&gt;&lt;code&gt;module&lt;span&gt;.&lt;/span&gt;exports &lt;span&gt;=&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
	&lt;span&gt;...&lt;/span&gt;
	&lt;span&gt;module&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
		&lt;span&gt;rules&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;[&lt;/span&gt;
			&lt;span&gt;...&lt;/span&gt;
			&lt;span&gt;{&lt;/span&gt;
				&lt;span&gt;test&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;\.mindmap.puml$&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
				&lt;span&gt;use&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;&apos;plantuml-mindmap-loader&apos;&lt;/span&gt;
			&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
			&lt;span&gt;...&lt;/span&gt;
		&lt;span&gt;]&lt;/span&gt;
	&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
	&lt;span&gt;...&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Note 1:&lt;/strong&gt; The &lt;code&gt;test&lt;/code&gt; value includes RegEx that specifies for files of what extension should the loader be used. In our case, we ask webpack to process any encountered &lt;code&gt;**.mindmap.puml&lt;/code&gt; with this new loader.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note 2:&lt;/strong&gt; The &lt;code&gt;test&lt;/code&gt; value should match the module definition in &lt;code&gt;index.d.ts&lt;/code&gt; file of the loader. That will ensure that every import is covered with an accompanying type set from module definition.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If the types from the plugin aren&apos;t visible for your IDE or webpack complains about missing types for your new imports, update &lt;code&gt;tsconfig.json&lt;/code&gt; in the target project to include the type definition like this:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;{&lt;/span&gt;
	...
	&lt;span&gt;&quot;include&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;[&lt;/span&gt;
		&lt;span&gt;&quot;src/**/*&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
		&lt;span&gt;&quot;node_modules/plantuml-mindmap-loader/index.d.ts&quot;&lt;/span&gt;
	&lt;span&gt;]&lt;/span&gt;
	...
&lt;span&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Import your custom format file into your code and work with it as if it was a JS object:&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;import&lt;/span&gt; testMindMap &lt;span&gt;from&lt;/span&gt; &lt;span&gt;&quot;./test.mindmap.puml&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;

&lt;span&gt;export&lt;/span&gt; &lt;span&gt;function&lt;/span&gt; &lt;span&gt;printMindMap&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt; &lt;span&gt;{&lt;/span&gt;
	console&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;navigationMindMap&lt;span&gt;,&lt;/span&gt; &lt;span&gt;null&lt;/span&gt;&lt;span&gt;,&lt;/span&gt; &lt;span&gt;2&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Results&lt;/h3&gt;
&lt;p&gt;If everything is done right, for such content of &lt;code&gt;test.mindmap.puml&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;pre&gt;&lt;code&gt;@startmindmap

* Root #tag1 #tag2
** Node 1 #tag3
*** Node 1.1
**** Node 1.1.1
** Node 2
*** Node 2.1

@endmindmap&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;you will get that output in your target project:&lt;/p&gt;
&lt;div&gt;&lt;pre&gt;&lt;code&gt;&lt;span&gt;{&lt;/span&gt;
  &lt;span&gt;&quot;id&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;3&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
  &lt;span&gt;&quot;label&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;&quot;Root&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
  &lt;span&gt;&quot;tags&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;[&lt;/span&gt;
    &lt;span&gt;&quot;#tag1&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
    &lt;span&gt;&quot;#tag2&quot;&lt;/span&gt;
  &lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
  &lt;span&gt;&quot;children&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;[&lt;/span&gt;
    &lt;span&gt;{&lt;/span&gt;
      &lt;span&gt;&quot;id&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;4&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
      &lt;span&gt;&quot;label&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;&quot;Node 1&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
      &lt;span&gt;&quot;tags&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;[&lt;/span&gt;
        &lt;span&gt;&quot;#tag3&quot;&lt;/span&gt;
      &lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
      &lt;span&gt;&quot;children&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;[&lt;/span&gt;
        &lt;span&gt;{&lt;/span&gt;
          &lt;span&gt;&quot;id&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;5&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
          &lt;span&gt;&quot;label&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;&quot;Node 1.1&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
          &lt;span&gt;&quot;tags&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
          &lt;span&gt;&quot;children&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;[&lt;/span&gt;
            &lt;span&gt;{&lt;/span&gt;
              &lt;span&gt;&quot;id&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;6&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
              &lt;span&gt;&quot;label&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;&quot;Node 1.1.1&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
              &lt;span&gt;&quot;tags&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
              &lt;span&gt;&quot;children&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;
            &lt;span&gt;}&lt;/span&gt;
          &lt;span&gt;]&lt;/span&gt;
        &lt;span&gt;}&lt;/span&gt;
      &lt;span&gt;]&lt;/span&gt;
    &lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
    &lt;span&gt;{&lt;/span&gt;
      &lt;span&gt;&quot;id&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;7&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
      &lt;span&gt;&quot;label&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;&quot;Node 2&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
      &lt;span&gt;&quot;tags&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
      &lt;span&gt;&quot;children&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;[&lt;/span&gt;
        &lt;span&gt;{&lt;/span&gt;
          &lt;span&gt;&quot;id&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;8&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
          &lt;span&gt;&quot;label&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;&quot;Node 2.1&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
          &lt;span&gt;&quot;tags&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;
          &lt;span&gt;&quot;children&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt; &lt;span&gt;[&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;
        &lt;span&gt;}&lt;/span&gt;
      &lt;span&gt;]&lt;/span&gt;
    &lt;span&gt;}&lt;/span&gt;
  &lt;span&gt;]&lt;/span&gt;
&lt;span&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Result &amp;amp; Beyond&lt;/h2&gt;
&lt;p&gt;By using webpack loaders in this manner, we can work with any text format in our JS projects. This approach isolates the specifics of parsing distinct text formats to the place that is the most suitable for that: the build process.&lt;/p&gt;
&lt;p&gt;Also, the validation of content embedded in a loader helps ensure that the static assets of this type are compatible with the latest version of your code.&lt;/p&gt;
&lt;p&gt;But it has at least one significant drawback that worth a mention:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;With that approach, you can work with static assets only. If a file should be located in a shared storage and on purpose fetched and processed in runtime, a different approach should be chosen.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nevertheless, this approach is helpful in many scenarios. That makes almost any static asset functional.&lt;/p&gt;
&lt;p&gt;Share other samples of inventive use of custom webpack loaders!&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I hope this can serve as a helpful guide when you&apos;ll be creating your own custom loaders. It&apos;s a powerful tool and it deserves more frequent and inventive use in your projects.&lt;/p&gt;
&lt;p&gt;The loader I created is also published to &lt;a href=&quot;https://www.npmjs.com/package/plantuml-mindmap-loader&quot;&gt;npm&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In future posts, I&apos;ll share more info about why I want to make PlantUML mind maps &lt;a href=&quot;https://en.wikipedia.org/wiki/First-class_citizen&quot;&gt;first-class citizens&lt;/a&gt; of my codebase. Adding these details here would inflate the article... There will be a day and there will be another story.&lt;/p&gt;
&lt;p&gt;Follow me so you don&apos;t miss these updates.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Securing Software Development: Integrating InfoSec and Scrum Teams]]></title><description><![CDATA[Introduction As a manager or a software engineer working with Scrum methodology, you might be very well aware of the underlying principles…]]></description><link>https://hopefullysurprising.com/processing-security-reports-with-scrum/</link><guid isPermaLink="false">https://hopefullysurprising.com/processing-security-reports-with-scrum/</guid><pubDate>Thu, 24 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;As a manager or a software engineer working with Scrum methodology, you might be very well aware of the underlying principles of an effective workflow that maximises the outcome of efforts by establishing a universal pipeline for feature requests from business to the market and back. But it shouldn&apos;t be surprising if not all the parts of your organisation follow the principles of the Agile process and Scrum in particular. Each organisation unit, each part of the overall process should be free to choose the approach that works the best.&lt;/p&gt;
&lt;p&gt;In a situation when requirements for a development team using Scrum come from external sources, it&apos;s critical to establish correct expectations on input and output of the sub-processes driven by different yet interacting teams.&lt;/p&gt;
&lt;p&gt;The key to creating successful process here is understanding that a robust Scrum team operates as a streamlined and effective pipeline, efficiently translating formulated business requests into tangible outcomes. However, when these requests arrive in unfamiliar formats, a disconnect emerges, hindering the realisation of objectives. In the worst cases, that might significantly impact feature delivery (for the whole team) due to prolonged investigation and/or implementation.&lt;/p&gt;
&lt;p&gt;In this material, we will consider a specific case of addressing &lt;a href=&quot;https://en.wikipedia.org/wiki/Information_security&quot;&gt;information security&lt;/a&gt;-related work in the most effective manner when requests for such work are coming from outside the team. We will set expectations on processing vulnerability reports to seamlessly flow the items to &lt;a href=&quot;https://www.scrum.org/resources/what-is-a-product-backlog&quot;&gt;Scrum backlog&lt;/a&gt;, facilitating this integration into established operational procedures. Addressing this disparity is essential to enable the team to manage these requests without impeding other items in the backlog.&lt;/p&gt;
&lt;h2&gt;Vision&lt;/h2&gt;
&lt;p&gt;The core solution revolves around defining precise communication expectations across multiple stakeholders - InfoSec professionals, IT managers, and the development team itself. This structured approach mirrors the concept of the &lt;a href=&quot;https://en.wikipedia.org/wiki/Adapter_pattern&quot;&gt;adapter pattern&lt;/a&gt;, transferring its essence from code implementation to real-world procedures.&lt;/p&gt;
&lt;p&gt;Clear communication plays a pivotal role, with InfoSec leaders gaining insights into the expected format for business requests, while the development team learns how to communicate changes&apos; outcomes. This mutual understanding empowers InfoSec professionals to give the right initial impulse to effective security remediation work and maintain an updated security state in both internal and external documentation.&lt;/p&gt;
&lt;p&gt;We&apos;re going to utilise the capabilities of a proficient Scrum team to implement change requests that follow the requirements the team puts to input (in a form of &lt;a href=&quot;https://www.scrum.org/resources/blog/walking-through-definition-ready&quot;&gt;Definition of Ready&lt;/a&gt; or a similar principle) by bridging the gap between teams following different approach to work organisation.&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;The solution lies in following a comprehensive, step-by-step guide that navigates the journey from a fresh security report to Scrum backlog stories and vice versa. This seamless integration ensures minimal disruption to the team&apos;s processes and capitalises on their comfort zone and work habits.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;InfoSec team: Initial report analysis.&lt;/strong&gt; The initial holistic report for one or multiple projects undergoes dissection by an InfoSec specialist, segregating it into independent units of work. This step ensures that vulnerabilities can be prioritised, estimated, and planned independently, fostering several benefits:
&lt;ul&gt;
&lt;li&gt;Parallel investigation and resolution of multiple items.&lt;/li&gt;
&lt;li&gt;Prevention of low-criticality issues obstructing more critical tasks.&lt;/li&gt;
&lt;li&gt;Enhanced accuracy in estimating smaller tasks, minimising uncertainty.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;InfoSec team: Issue categorisation.&lt;/strong&gt; Issues are classified into two categories: infrastructure issues and application issues. Infrastructure concerns are directed to DevOps or Ops teams, while application issues progress through the provided guide.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;InfoSec team: Story Creation:&lt;/strong&gt; Drafts for individual vulnerabilities translate into user stories within the chosen task management system. These stories are assigned to a delivery manager or another responsible individual who is familiar with both the system and team processes. Story creation emphasises several key points for the team:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Background Information:&lt;/strong&gt; Detailed understanding of the vulnerability&apos;s nature, affected components, and contextual conditions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reproducibility:&lt;/strong&gt; Steps for replicating the issue in a controlled environment, vital for QA and automated testing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Description:&lt;/strong&gt; Recommendations for addressing the problem, suggesting common approaches. When the solution is unclear, the story&apos;s objective is to identify a solution.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Acceptance Criteria:&lt;/strong&gt; Criteria defining successful vulnerability resolution - what we should see to say that we have achieved the goal.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These stories are systematically labeled, tagged, or grouped for streamlined accessibility.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;IT Manager: Oversight.&lt;/strong&gt; A delivery manager reviews the created stories, ensuring they meet the outlined requirements. Additionally, they assess the need for further task breakdowns, particularly for less atomic requests.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IT Manager: Ensuring System Alignment.&lt;/strong&gt; The recommendations for vulnerability addressing are evaluated against the system&apos;s perspective. If necessary, the selected strategy is adjusted to align with the system&apos;s values and patterns. Examples include:
&lt;ul&gt;
&lt;li&gt;Addressing verbose error messages in a mobile app by updating the backend API.&lt;/li&gt;
&lt;li&gt;Consolidating updates to a dependency by upgrading to the current version during the current process instead of the minimal recommended one.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The delivery manager ensures this alignment is communicated effectively to the product owner and team, with information enabling the identification of issues throughout the application.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;IT Manager. Assignment and Monitoring.&lt;/strong&gt; Stories are assigned to releases based on vulnerability criticality, and tasks are allocated to corresponding teams. A dashboard or filter that presents tagged vulnerability remediation tasks is required at this stage, providing visibility on statuses and release timelines. This process allows for real-time monitoring and adjustments for both IT manager and InfoSec team.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Result &amp;amp; Beyond&lt;/h2&gt;
&lt;p&gt;The structured procedure ensures that stakeholders can operate within their preferred formats and methodologies while seamlessly integrating security concerns. This approach minimises the impact on the team&apos;s ongoing plans, as security issues are addressed with minimal disruption.&lt;/p&gt;
&lt;p&gt;This methodology also minimises the risk of unexpected challenges when addressing vulnerabilities. By tailoring recommendations to the project situation, the likelihood of encountering unforeseen obstacles is significantly decreased.&lt;/p&gt;
&lt;p&gt;The InfoSec team benefits from real-time visibility into project plans and progress, enabling swift reactions to deprioritisation and successful deliveries as their capacity allows.&lt;/p&gt;
&lt;p&gt;While the process is comprehensive, avenues for improvement remain:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Educating InfoSec team and other peers about Scrum methodologies fosters better story creation.&lt;/li&gt;
&lt;li&gt;Educating the Scrum team on InfoSec team&apos;s priorities enhances understanding of the requests and more accurate issue addressing.&lt;/li&gt;
&lt;li&gt;Involvement of senior developers in processing stories streamlines the process and helps scale the process when the vulnerability reports cover multiple projects.&lt;/li&gt;
&lt;li&gt;Measuring lead time of the security-related requests in your projects must lead to well-informed decision on claimed remediation time for security vulnerabilities that might lead to establishing a &lt;a href=&quot;https://nucleussec.com/blog/how-to-define-vulnerability-remediation-slas-shortcuts&quot;&gt;vulnerability remediation SLA&lt;/a&gt; if it hasn&apos;t happened yet.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Within our organisation, this process has been helpful in rapidly addressing security concerns while maintaining focus on project innovation. We welcome your thoughts on this approach, proposed enhancements, and any additional recommendations or queries.&lt;/p&gt;
&lt;p&gt;Let&apos;s save time for beautiful innovations through using well-optimised processes for routine tasks!&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Enabling Local Editing for MkDocs Documentation with IDE]]></title><description><![CDATA[Introduction There are a million good reasons to start writing documentation. Documentation is the key to preserving acquired knowledge…]]></description><link>https://hopefullysurprising.com/mkdocs-local-edit-button/</link><guid isPermaLink="false">https://hopefullysurprising.com/mkdocs-local-edit-button/</guid><pubDate>Sat, 19 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;There are a million good reasons to start writing documentation. Documentation is the key to preserving acquired knowledge indefinitely - for others and for your future self (who might be quite different if things are going well). As essential tool as documentation must promote the idea of keeping it up-to-date by ensuring easy access to editing pages, so that including more details to existing information and putting focus on right things is simple.&lt;/p&gt;
&lt;p&gt;While some documentation management systems like Confluence, Notion and others allow in-place content editing, systems with serialisable content stored separately, such as MkDocs, it&apos;s not that straightforward. In this article, we will discuss MkDocs that offers an out-of-the-box solution for that.&lt;/p&gt;
&lt;p&gt;MkDocs has the ability to enhance pages with an &quot;Edit&quot; button that allows opening the source code of a specific page in Git repository. However, most guides (including &lt;a href=&quot;https://squidfunk.github.io/mkdocs-material/setup/adding-a-git-repository/?h=edit+uri#code-actions&quot;&gt;official documentation&lt;/a&gt;) focus on scenarios involving cloud-based Git repositories. That&apos;s a quite cool feature, especially, if documentation maintenance is public but that&apos;s not as helpful for maintainers working on pages locally.&lt;/p&gt;
&lt;p&gt;In this material, let&apos;s explore how to make &quot;Edit&quot; button open pages in modern IDEs using custom &lt;a href=&quot;https://en.wikipedia.org/wiki/Uniform_Resource_Identifier#Syntax&quot;&gt;URI schemes&lt;/a&gt;, allowing for a seamless transition from viewing to editing.&lt;/p&gt;
&lt;h2&gt;Vision&lt;/h2&gt;
&lt;p&gt;The objective is simple: equip every page in a &lt;a href=&quot;https://squidfunk.github.io/mkdocs-material/&quot;&gt;MkDocs Material&lt;/a&gt; generated documentation site with an &quot;Edit&quot; button that opens the corresponding source markdown file in &lt;a href=&quot;https://code.visualstudio.com/&quot;&gt;VS Code&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Understanding the Configuration:&lt;/strong&gt; Familiarise yourself with the &lt;code&gt;edit_uri&lt;/code&gt; configuration option in the MkDocs &lt;a href=&quot;https://www.mkdocs.org/user-guide/configuration/#edit_uri&quot;&gt;documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Unveiling VS Code&apos;s Power:&lt;/strong&gt; Explore the &lt;a href=&quot;https://code.visualstudio.com/docs/editor/command-line#_opening-vs-code-with-urls&quot;&gt;documentation&lt;/a&gt; on launching VS Code using custom URLs. This knowledge will empower our &quot;Edit&quot; button to spring into action.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Crafting the Base URL:&lt;/strong&gt; Create a base URL that directs users to the directory housing your documentation. For instance:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;vscode://file/${FULL_PATH_TO_CONTENT_DIRECTORY}&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; If you&apos;re an &lt;a href=&quot;https://code.visualstudio.com/insiders&quot;&gt;VS Code Insiders&lt;/a&gt; user, kick off the URL with &lt;code&gt;vscode-insiders://&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configuring &lt;code&gt;edit_uri&lt;/code&gt;:&lt;/strong&gt; Modify your &lt;code&gt;mkdocs.yml&lt;/code&gt; file, setting the &lt;code&gt;edit_uri&lt;/code&gt; option to your created URL:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;edit_uri: vscode-insiders://file/Users/me/Development/my-project/documentation/content/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note:&lt;/em&gt; If you aren&apos;t integrating a Git link, you can omit the optional &lt;code&gt;repo_url&lt;/code&gt; &lt;a href=&quot;https://www.mkdocs.org/user-guide/configuration/#repo_url&quot;&gt;setting&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Enabling the &quot;Edit&quot; Button:&lt;/strong&gt; Activate the &quot;Edit&quot; button &lt;a href=&quot;https://squidfunk.github.io/mkdocs-material/setup/adding-a-git-repository/?h=content+action+edit#code-actions&quot;&gt;feature&lt;/a&gt; within MkDocs Material by updating your &lt;code&gt;mkdocs.yml&lt;/code&gt; as follows:&lt;/p&gt;
&lt;div&gt;&lt;pre&gt;&lt;code&gt; theme:
 	features:
 		- content.action.edit&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Final Touch:&lt;/strong&gt; Now, perform the documentation build process.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Results and Beyond&lt;/h3&gt;
&lt;p&gt;With these adjustments, every page of your documentation will get an &quot;Edit&quot; button, making the source file open in VS Code. If this solution helps you in your workflow, consider potential expansions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Conditional URI Setup:&lt;/strong&gt; Leverage environment variables to configure the URI dynamically. For local use, it fires up VS Code; for hosting, it points to the Git repository.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Beyond VS Code:&lt;/strong&gt; If your preferred IDE differs from VS Code, explore available URI schemes that align with your choice.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Theme Compatibility:&lt;/strong&gt; If you&apos;re not using the Material theme, verify whether your theme supports the &quot;Edit&quot; button feature.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This approach streamlines local documentation edits while leaving room for enjoying well-organised navigation between built documentation pages. Join the discussion in the comments if you have insights to share about the potential expansions or the overall concept.&lt;/p&gt;
&lt;p&gt;Let&apos;s document effectively with minimal effort!&lt;/p&gt;</content:encoded></item></channel></rss>