<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Hopefully Surprising</title>
    <description>The latest articles on Forem by Hopefully Surprising (@hopefully_surprising).</description>
    <link>https://forem.com/hopefully_surprising</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1138403%2Fc6102d6d-00e8-4ea0-9b79-fc5fcd37d5ec.png</url>
      <title>Forem: Hopefully Surprising</title>
      <link>https://forem.com/hopefully_surprising</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/hopefully_surprising"/>
    <language>en</language>
    <item>
      <title>SonarQube Community Edition: Comprehensive guide for a free personal setup</title>
      <dc:creator>Hopefully Surprising</dc:creator>
      <pubDate>Tue, 03 Oct 2023 16:11:03 +0000</pubDate>
      <link>https://forem.com/hopefully_surprising/sonarqube-community-edition-comprehensive-guide-for-a-free-personal-setup-4509</link>
      <guid>https://forem.com/hopefully_surprising/sonarqube-community-edition-comprehensive-guide-for-a-free-personal-setup-4509</guid>
      <description>&lt;p&gt;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 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="https://hopefullysurprising.medium.com/building-a-typescript-compatible-webpack-loader-a-plantuml-mind-map-example-c0b3ec9bc033"&gt;Creating a custom webpack loader for text files, compatible with TypeScript. PlantUML mind map example&lt;/a&gt;). In addition to that, I'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's possible to run the server and SonarScanner &lt;a href="https://docs.sonarsource.com/sonarqube/latest/setup-and-upgrade/install-the-server/#installing-sonarqube-from-the-zip-file"&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'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'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="https://docs.sonarsource.com/sonarqube/latest/setup-and-upgrade/install-the-server/#installing-the-database"&gt;supports different databases&lt;/a&gt; so we can use this advantage. The key for me is to use the database that you'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's Postgres. I'm not sharing the instructions on how to install Postgres itself but here is the &lt;a href="https://www.postgresql.org/download/linux/ubuntu/"&gt;link to official documentation&lt;/a&gt; about installing Postgres 12 on Ubuntu. SonarQube's &lt;a href="https://docs.sonarsource.com/sonarqube/latest/setup-and-upgrade/install-the-server/#example-docker-compose-configuration"&gt;Docker Compose example&lt;/a&gt; refers to PostgreSQL 12 so I'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't forget to replace &lt;code&gt;SONAR_DB_PASSWORD&lt;/code&gt; with the real one you'll be using.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE SCHEMA sonarqube;
CREATE USER sonarqube WITH PASSWORD 'SONAR_DB_PASSWORD';
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 class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&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 class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&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'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="https://docs.docker.com/engine/reference/commandline/logs/"&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'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'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 "My Account" -&amp;gt; "Security".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---D-BXIb0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u4f2nsf47mugcspixm44.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---D-BXIb0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u4f2nsf47mugcspixm44.png" alt="Create a global token" width="800" height="326"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Other options are explained &lt;a href="https://docs.sonarsource.com/sonarqube/latest/user-guide/user-account/generating-and-using-tokens/#generating-a-token"&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'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 "Create Project" and choose "Manually". Input display name and the project key, choose the default branch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QH9PSZLF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k9oahbagsk6vcg1l1e5t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QH9PSZLF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k9oahbagsk6vcg1l1e5t.png" alt="Create a project" width="800" height="400"&gt;&lt;/a&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'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;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aWePk6pW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lqwq3cu6uic7n0t7ehpr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aWePk6pW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lqwq3cu6uic7n0t7ehpr.png" alt="Choose global settings" width="800" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the opened project page, choose "Locally" for scanning setup.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--t2FXWxwG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rndubm2zbksej4shuoap.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--t2FXWxwG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rndubm2zbksej4shuoap.png" alt="Choose Locally" width="800" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Provide the global token you generated before and click Next.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8WoPe3vI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cg0s8vvrc8595z4ne9z4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8WoPe3vI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cg0s8vvrc8595z4ne9z4.png" alt="Select existing token" width="800" height="410"&gt;&lt;/a&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're going to scan with SonarQube, create &lt;code&gt;sonar-project.properties&lt;/code&gt; file with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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'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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run \
    --rm \
    -e SONAR_HOST_URL="http://localhost:9000" \
    -e SONAR_SCANNER_OPTS="-Dproject.settings=sonar-project.properties" \
    -e SONAR_TOKEN="SONAR_GLOBAL_TOKEN" \
    -v "./software/packages/plantuml-mindmap-loader:/usr/src" \
    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's completed you can enjoy the code quality assessment results in SonarQube&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Hw_nru-b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yya9vakvqetlijerisyv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Hw_nru-b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yya9vakvqetlijerisyv.png" alt="Enjoy the results" width="800" height="411"&gt;&lt;/a&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;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Hl9tlvla--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sk0pg6v80wn0zdb4vw68.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Hl9tlvla--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sk0pg6v80wn0zdb4vw68.png" alt="See issue list" width="800" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ksb3HMIG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5wmxaoa471y9x0vx1h09.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ksb3HMIG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5wmxaoa471y9x0vx1h09.png" alt="See issue details" width="800" height="407"&gt;&lt;/a&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="https://github.com/SonarSource/sonarlint-vscode"&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="https://docs.sonarsource.com/sonarqube/latest/user-guide/sonarlint-connected-mode/"&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="https://github.com/SonarSource/sonarlint-vscode"&gt;SonarLint extension&lt;/a&gt; in VS Code.&lt;/li&gt;
&lt;li&gt;Go to the "SonarLint" tab and click at the plus button in "Connected Mode" 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;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Pp9q6JY3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cec3xdhzfhmd37qmfj7p.png" alt="Setup SonarLint" width="800" height="341"&gt;
&lt;/li&gt;
&lt;li&gt;Click "Save Connection".&lt;/li&gt;
&lt;li&gt;For this connection, click the plus button for "Add project binding".&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;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kOKXYeem--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ddfyn369ium8yeq43sri.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kOKXYeem--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ddfyn369ium8yeq43sri.png" alt="See suggestions" width="800" height="539"&gt;&lt;/a&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'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't intimidate you from trying this local setup. In fact, it'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'll enjoy it.&lt;/p&gt;

</description>
      <category>sonarqube</category>
      <category>codequality</category>
      <category>technicaldebt</category>
    </item>
    <item>
      <title>Our Experiment with AI-Powered Dev Tools: The GitHub Copilot Experience of One Company</title>
      <dc:creator>Hopefully Surprising</dc:creator>
      <pubDate>Tue, 26 Sep 2023 15:33:35 +0000</pubDate>
      <link>https://forem.com/hopefully_surprising/our-experiment-with-ai-powered-dev-tools-the-github-copilot-experience-of-one-company-46ml</link>
      <guid>https://forem.com/hopefully_surprising/our-experiment-with-ai-powered-dev-tools-the-github-copilot-experience-of-one-company-46ml</guid>
      <description>&lt;p&gt;We provided 5 of our developers with access to GitHub Copilot for 2 months to find out if we need to buy it for everyone. Here are the unexpected results of the experiment&lt;/p&gt;

&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="https://www.tabnine.com"&gt;TabNine&lt;/a&gt;, &lt;a href="https://openai.com/blog/chatgpt"&gt;ChatGPT 3 &amp;amp; 4&lt;/a&gt; and, finally, &lt;a href="https://copilot.github.com"&gt;GitHub Copilot&lt;/a&gt; that wraps a special version of OpenAI's &lt;a href="https://openai.com/blog/openai-codex"&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't what I expected to see. And, sorry for the spoiler, we changed our plans - we'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't involve long-term data collection by Microsoft. The prompts are discarded immediately after providing (see &lt;a href="https://docs.github.com/en/site-policy/privacy-policies/github-copilot-for-business-privacy-statement#prompts-1"&gt;details&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;We didn't include &lt;a href="https://docs.github.com/en/copilot/github-copilot-chat/using-github-copilot-chat"&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'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="https://github.com/TerminalFi/LSP-copilot"&gt;one&lt;/a&gt; looks interesting but we didn't spend time on it).&lt;/p&gt;

&lt;h3&gt;
  
  
  When it'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'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="https://en.wikipedia.org/wiki/Id,_ego_and_super-ego"&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'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's not lazy, it will write what'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'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'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's why it's in this blog. Every developer in our experiment came to a simple conclusion: if the company doesn't provide the license, they wouldn'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'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;

</description>
      <category>ai</category>
      <category>chatgpt</category>
      <category>githubcopilot</category>
    </item>
    <item>
      <title>Building a TypeScript-Compatible Webpack Loader: A PlantUML Mind Map Example</title>
      <dc:creator>Hopefully Surprising</dc:creator>
      <pubDate>Wed, 13 Sep 2023 14:44:38 +0000</pubDate>
      <link>https://forem.com/hopefully_surprising/building-a-typescript-compatible-webpack-loader-a-plantuml-mind-map-example-49jl</link>
      <guid>https://forem.com/hopefully_surprising/building-a-typescript-compatible-webpack-loader-a-plantuml-mind-map-example-49jl</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In the historical book &lt;a href="https://pragprog.com/titles/tpp20/the-pragmatic-programmer-20th-anniversary-edition/"&gt;"The Pragmatic Programmer"&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 "The Basic Tools: Text Manipulation"). 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="https://plantuml.com/mindmap-diagram"&gt;PlantUML's mind maps&lt;/a&gt; might be a more preferable option for storing &lt;a href="https://en.wikipedia.org/wiki/Serialization"&gt;serialised&lt;/a&gt; trees.&lt;/p&gt;

&lt;p&gt;That'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="https://www.npmjs.com/package/plantuml-mindmap-loader"&gt;npm&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Source code in &lt;a href="https://github.com/hopefullysurprising/plantuml-mindmap-loader"&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't require passing non-common extension files separately.&lt;/p&gt;

&lt;p&gt;In this tutorial, we will create a &lt;a href="https://webpack.js.org/loaders/"&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 class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;plantuml-mindmap-loader &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&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'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 class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm 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 class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; @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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"peerDependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"webpack"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^4.0.0 || ^5.0.0"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; It's a webpack loader, hence, webpack is always installed in a "parent" 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 class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lib/index.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"types"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"index.d.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;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 class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"es2016"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"commonjs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"allowJs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"outDir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./lib"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"esModuleInterop"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"strict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"include"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"**/*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"exclude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"**/*.spec.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"coverage/**/*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;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 class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;validate&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;schema-utils&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;LoaderContext&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;webpack&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;parseMindMap&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/parseMindMap&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;LoaderProperties&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// `as` is for making the type match `JSONSchema7`&lt;/span&gt;
    &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// `as` is for making the type match `JSONSchema7`&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Should match the definition in index.d.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;PlantUMLMindMapNode&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Order number of the line in the file&lt;/span&gt;
    &lt;span class="nl"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PlantUMLMindMapNode&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LoaderContext&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;LoaderProperties&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// 1. Get options that can be provided to the loader&lt;/span&gt;
    &lt;span class="c1"&gt;// via webpack loader configuration.&lt;/span&gt;
    &lt;span class="c1"&gt;// Also, validate these options.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getOptions&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;plantuml-mindmap-loader&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;baseDataPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;options&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

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

    &lt;span class="c1"&gt;// 3. We return a string that is a valid JS code&lt;/span&gt;
    &lt;span class="c1"&gt;// that will be injected as a result of file loading.&lt;/span&gt;
    &lt;span class="c1"&gt;// In our case, we simply stringify the JSON object.&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`export default &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;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's not the core subject of this article, let me only share the &lt;a href="https://github.com/hopefullysurprising/plantuml-mindmap-loader/blob/main/src/parseMindMap.ts"&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 class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;declare&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*.mindmap.puml&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// Should match the definition in index.ts&lt;/span&gt;
    &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;PlantUMLMindMapNode&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Number of the line in the file&lt;/span&gt;
        &lt;span class="nl"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
        &lt;span class="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PlantUMLMindMapNode&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PlantUMLMindMapNode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That should be enough for now. If you're interested in how it might be covered with unit-tests, take a look at the &lt;a href="https://github.com/hopefullysurprising/plantuml-mindmap-loader"&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's needed for building only)
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-dev&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 class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="na"&gt;module&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;...&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;mindmap.puml$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;plantuml-mindmap-loader&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;...&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&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'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 class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"include"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"src/**/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"node_modules/plantuml-mindmap-loader/index.d.ts"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;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 class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;testMindMap&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./test.mindmap.puml&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;printMindMap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;navigationMindMap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Root"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"#tag1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"#tag2"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"children"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Node 1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"#tag3"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"children"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Node 1.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"children"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Node 1.1.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"children"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Node 2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"children"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Node 2.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"children"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  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'll be creating your own custom loaders. It'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="https://www.npmjs.com/package/plantuml-mindmap-loader"&gt;npm&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In future posts, I'll share more info about why I want to make PlantUML mind maps &lt;a href="https://en.wikipedia.org/wiki/First-class_citizen"&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't miss these updates.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>webpack</category>
      <category>plantuml</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Securing Software Development: Integrating InfoSec and Scrum Teams</title>
      <dc:creator>Hopefully Surprising</dc:creator>
      <pubDate>Thu, 24 Aug 2023 08:01:33 +0000</pubDate>
      <link>https://forem.com/hopefully_surprising/seamlessly-integrating-informational-security-with-scrum-teams-a-comprehensive-guide-i1l</link>
      <guid>https://forem.com/hopefully_surprising/seamlessly-integrating-informational-security-with-scrum-teams-a-comprehensive-guide-i1l</guid>
      <description>&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'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'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="https://en.wikipedia.org/wiki/Information_security"&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="https://www.scrum.org/resources/what-is-a-product-backlog"&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="https://en.wikipedia.org/wiki/Adapter_pattern"&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' 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'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="https://www.scrum.org/resources/blog/walking-through-definition-ready"&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'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;p&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;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Background Information:&lt;/strong&gt; Detailed understanding of the vulnerability'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'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.
These stories are systematically labeled, tagged, or grouped for streamlined accessibility.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&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;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;IT Manager: Ensuring System Alignment.&lt;/strong&gt; The recommendations for vulnerability addressing are evaluated against the system's perspective. If necessary, the selected strategy is adjusted to align with the system's values and patterns. Examples include:&lt;/p&gt;

&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.
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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&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;/p&gt;&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'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'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="https://nucleussec.com/blog/how-to-define-vulnerability-remediation-slas-shortcuts"&gt;vulnerability remediation SLA&lt;/a&gt; if it hasn'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's save time for beautiful innovations through using well-optimised processes for routine tasks!&lt;/p&gt;

</description>
      <category>scrum</category>
      <category>infosec</category>
      <category>management</category>
    </item>
    <item>
      <title>Enabling Local Editing for MkDocs Documentation with IDE</title>
      <dc:creator>Hopefully Surprising</dc:creator>
      <pubDate>Sun, 13 Aug 2023 18:22:09 +0000</pubDate>
      <link>https://forem.com/hopefully_surprising/enhancing-the-edit-button-in-mkdocs-based-documentation-494h</link>
      <guid>https://forem.com/hopefully_surprising/enhancing-the-edit-button-in-mkdocs-based-documentation-494h</guid>
      <description>&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'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 "Edit" button that allows opening the source code of a specific page in Git repository. However, most guides (including &lt;a href="https://squidfunk.github.io/mkdocs-material/setup/adding-a-git-repository/?h=edit+uri#code-actions"&gt;official documentation&lt;/a&gt;) focus on scenarios involving cloud-based Git repositories. That's a quite cool feature, especially, if documentation maintenance is public but that's not as helpful for maintainers working on pages locally.&lt;/p&gt;

&lt;p&gt;In this material, let's explore how to make "Edit" button open pages in modern IDEs using custom &lt;a href="https://en.wikipedia.org/wiki/Uniform_Resource_Identifier#Syntax"&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="https://squidfunk.github.io/mkdocs-material/"&gt;MkDocs Material&lt;/a&gt; generated documentation site with an "Edit" button that opens the corresponding source markdown file in &lt;a href="https://code.visualstudio.com/"&gt;VS Code&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&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="https://www.mkdocs.org/user-guide/configuration/#edit_uri"&gt;documentation&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unveiling VS Code's Power:&lt;/strong&gt; Explore the &lt;a href="https://code.visualstudio.com/docs/editor/command-line#_opening-vs-code-with-urls"&gt;documentation&lt;/a&gt; on launching VS Code using custom URLs. This knowledge will empower our "Edit" button to spring into action.&lt;/li&gt;
&lt;li&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;code&gt;vscode://file/${FULL_PATH_TO_CONTENT_DIRECTORY}&lt;/code&gt;
&lt;em&gt;Note:&lt;/em&gt; If you're an &lt;a href="https://code.visualstudio.com/insiders"&gt;VS Code Insiders&lt;/a&gt; user, kick off the URL with &lt;code&gt;vscode-insiders://&lt;/code&gt;.&lt;/li&gt;
&lt;li&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;code&gt;edit_uri: vscode-insiders://file/Users/me/Development/my-project/documentation/content/&lt;/code&gt;.
&lt;em&gt;Note:&lt;/em&gt; If you aren't integrating a Git link, you can omit the optional &lt;code&gt;repo_url&lt;/code&gt; &lt;a href="https://www.mkdocs.org/user-guide/configuration/#repo_url"&gt;setting&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enabling the "Edit" Button:&lt;/strong&gt; Activate the "Edit" button &lt;a href="https://squidfunk.github.io/mkdocs-material/setup/adding-a-git-repository/?h=content+action+edit#code-actions"&gt;feature&lt;/a&gt; within MkDocs Material by updating your &lt;code&gt;mkdocs.yml&lt;/code&gt; as follows:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    theme:
        features:
            - content.action.edit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Final. &lt;strong&gt;Final Touch:&lt;/strong&gt; Now, perform the documentation build process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Results and Beyond
&lt;/h3&gt;

&lt;p&gt;With these adjustments, every page of your documentation will get an "Edit" 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're not using the Material theme, verify whether your theme supports the "Edit" 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's document effectively with minimal effort!&lt;/p&gt;

</description>
      <category>mkdocs</category>
      <category>documentation</category>
      <category>vscode</category>
    </item>
  </channel>
</rss>
