<?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: jeremiec</title>
    <description>The latest articles on Forem by jeremiec (@jeremiec).</description>
    <link>https://forem.com/jeremiec</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%2F500728%2Fd7682064-22a0-438e-b896-0fc8f71fa5be.png</url>
      <title>Forem: jeremiec</title>
      <link>https://forem.com/jeremiec</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/jeremiec"/>
    <language>en</language>
    <item>
      <title>Resources to Learn Management as a Software Engineer</title>
      <dc:creator>jeremiec</dc:creator>
      <pubDate>Tue, 18 Jun 2024 12:03:00 +0000</pubDate>
      <link>https://forem.com/jeremiec/resources-to-learn-management-as-a-software-engineer-431c</link>
      <guid>https://forem.com/jeremiec/resources-to-learn-management-as-a-software-engineer-431c</guid>
      <description>&lt;h2&gt;
  
  
  Why Learn Management as a Software Engineer?
&lt;/h2&gt;

&lt;p&gt;First, you don't have to. Most companies now understand that it's critical to offer individual contributor tracks for seniors.&lt;/p&gt;

&lt;p&gt;This post is for those who want to become better managers, even if pushed by circumstances.&lt;br&gt;
To be frank, coding is pretty easy, and you can overcome most problems with time and collaboration. Sadly, managing humans is a lot more complex, and fixing errors is not a simple git push away.&lt;/p&gt;

&lt;p&gt;In my opinion, I started managing a bit too early as I was still heavily maturing (and who still isn't), and even though I wanted to do an excellent job, I had plenty of failures.&lt;br&gt;
Here are a few recommendations so you get a head start and avoid the cost of learning management on people.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources to Learn Management
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;How to Win Friends and Influence People&lt;/strong&gt; by Dale Carnegie: Yes, I was butt-hurt when my previous CTO suggested this book to me. But once you get past the ego-triggering title, it's a nice book that can be a good reminder regarding a few things you should do: smile a lot, call people by name, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Thanks for the Feedback: The Science and Art of Receiving Feedback Well&lt;/strong&gt; by Douglas Stone and Sheila Heen: An amazing book both for receiving feedback, a gift for progression. As a leader, you need to show that you are open to feedback and that you take it well. Obviously important in giving feedback and teaching people to use it as a tool for progression.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Engineering Management for the Rest of Us&lt;/strong&gt; by Sarah Drasner: A great to-the-point book about what engineering management entails, what's your role, etc. &lt;a href="https://www.engmanagement.dev/"&gt;https://www.engmanagement.dev/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trillion Dollar Coach: The Leadership Handbook of Silicon Valley's Bill Campbell&lt;/strong&gt; by Eric Schmidt, Jonathan Rosenberg, and Alan Eagle: An interesting book about what being a coach means and how to be great at it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I highly recommend learning about negotiation as well. The best book I found regarding this subject is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Never Split the Difference: Negotiating as if Your Life Depended on It&lt;/strong&gt; by Chris Voss and Tahl Raz: An exceptional book about negotiation, not as a taboo technique or some secret skill but more as an everyday activity you always use.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I used to recommend &lt;em&gt;Getting to Yes: Negotiating Agreement Without Giving In&lt;/em&gt; by Roger Fisher, William L. Ury, and Bruce Patton, but I find it to be less applicable in real interaction, even if some notions are really interesting, like having a no-deal fallback (maybe you don't want to negotiate).&lt;/p&gt;

&lt;p&gt;If you have any recommendation, please do comment them below or &lt;a href="https://twitter.com/ChauvelJeremie"&gt; contact me on twitter&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://unsplash.com/photos/white-and-blue-printer-paper-mo3FOTG62ao"&gt;Cover image&lt;/a&gt; by &lt;a href="https://unsplash.com/@shiromanikant"&gt;Shiromani Kant&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>management</category>
      <category>career</category>
      <category>learning</category>
    </item>
    <item>
      <title>A tale about migrating a 200 entries Gatsby blog untouched for 3 years to Astro</title>
      <dc:creator>jeremiec</dc:creator>
      <pubDate>Sun, 16 Jun 2024 12:19:00 +0000</pubDate>
      <link>https://forem.com/jeremiec/a-tale-about-migrating-a-200-entries-gatsby-blog-untouched-for-3-years-to-astro-2n7b</link>
      <guid>https://forem.com/jeremiec/a-tale-about-migrating-a-200-entries-gatsby-blog-untouched-for-3-years-to-astro-2n7b</guid>
      <description>&lt;h2&gt;
  
  
  A little context about the tech blog and the migration
&lt;/h2&gt;

&lt;p&gt;I was working at Theodo, a high-end service company. As part of our diverse missions, we would often have to solve some interesting business challenges using interesting technical solutions.&lt;br&gt;
To share those learnings with the world, we have a technical blog at &lt;a href="https://blog.theodo.com/"&gt;https://blog.theodo.com/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Over the years, the blog was migrated from WordPress to Gatsby for blazing fast performance and nice features. Blazing being about a medium 60 &lt;a href="https://pagespeed.web.dev/"&gt;lighthouse score&lt;/a&gt; for a mostly static site, not very blazing. Sadly, when writing new articles, a slightly annoying 5 minutes boot time was required before any change could be shown. It was about 2 years after we started using &lt;a href="https://vitejs.dev/"&gt;Vitejs&lt;/a&gt; in production for most SPA clients, and the slowness was terrible.&lt;/p&gt;

&lt;p&gt;A nice workaround was to only load the last given month of articles, to reduce Gatsby's internal query resolution in development, allowing a reasonable boot time.&lt;/p&gt;

&lt;h3&gt;
  
  
  More reasons to quit Gatsby
&lt;/h3&gt;

&lt;p&gt;After a few articles on the blog, I was curious. Could I improve the developer experience while keeping the user experience at least at the same level? I wanted to dive into our Gatsby blog internals to find if I could improve query resolution and dev server boot time. Sadly, after learning about Gatsby and its abstraction of using a GraphQL layer to aggregate all data from any data source, I was at a loss. GraphQL can be great in some cases, like using Relay to send only one request on a client to load a full page worth of data. GraphQL as an abstraction layer to query data over markdown files, not as great. Looking at those queries, it felt like seeing an ant being squashed by a steamroller.&lt;/p&gt;

&lt;p&gt;Another aspect of the long build time was our continuous deployment pipeline: the site was deployed on Cloudflare Pages, and the build time was often big enough to encounter timeouts.&lt;/p&gt;

&lt;p&gt;Gatsby itself seemed less and less maintained, with very little activity on the main repository.&lt;/p&gt;

&lt;p&gt;Lastly, for a blog consisting mainly of text and images, we were shipping the full power of React.js. Quite overkill to toggle a search bar.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reasons to consider Astro
&lt;/h3&gt;

&lt;p&gt;Astro, on the other hand, had excellent press. Beginning with pristine documentation, &lt;a href="https://docs.astro.build/en/guides/migrate-to-astro/from-gatsby/"&gt;including a migrate from Gatsby entry&lt;/a&gt;. From a few experiments on other projects, it was quite a solid framework with a great developer experience. Importantly, there was little glass ceiling in terms of framework features: we get to keep React.js and JSX.&lt;/p&gt;

&lt;p&gt;An incredibly simpler model regarding markdown files as blog post entry collections with built-in validation of frontmatter, excellent typing, etc.&lt;/p&gt;

&lt;p&gt;Built on top of Vite, meaning a faster boot time in development (spoiler: about 20s for 200 entries with no optimizations), meaning fast hot module replacement.&lt;/p&gt;

&lt;p&gt;A golden path regarding performance, with the island architecture, meaning no client-side JavaScript for static content.&lt;/p&gt;

&lt;h2&gt;
  
  
  The migration from Gatsby to Astro
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Migrating from StyledComponents to Tailwind
&lt;/h3&gt;

&lt;p&gt;First of all, as the codebase was quite old and as I didn't want to bring more tech than what was required, I started to migrate my few React components on Gatsby from StyledComponent (a great CSS-in-JS solution) to &lt;a href="https://tailwindcss.com/"&gt;Tailwind CSS&lt;/a&gt;. Mostly because I wanted to see if I could measure the impact of moving from CSS-in-JS to pure CSS. The second goal was to allow Astro to run without client-side JS. To do so, I either needed to set up StyledComponent in Astro or migrate to Tailwind. Tailwind is documented and largely used on most projects now, and I was curious about the performance impact.&lt;/p&gt;

&lt;p&gt;The migration was actually easier than anticipated. Mostly done in the span of a few days, I had the privilege of allowing myself breaking changes on some design (like using Tailwind &lt;code&gt;prose&lt;/code&gt; for blog posts).&lt;/p&gt;

&lt;p&gt;One last "find the 7 errors game" later, the migration was live.&lt;/p&gt;

&lt;p&gt;For curiosity's sake, I measured the performance of some pages. The verdict was both surprising and anticlimactic. For such simple pages, there was absolutely no difference performance-wise. User experience-wise, I used the opportunity to fix a few flashes of incorrectly styled content (think JS responsive), meaning a slight improvement unrelated to Tailwind.&lt;/p&gt;

&lt;p&gt;My biggest known dependency was solved, it was time to write some Astro!&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating an Astro shell
&lt;/h3&gt;

&lt;p&gt;After a quick setup using Astro &lt;code&gt;create-app&lt;/code&gt; and adding React and Tailwind dependencies, it was time to recreate the shell pages. I copied 2 posts and got to work on the main homepage, which is just a basic post listing page. Let's go!&lt;/p&gt;

&lt;p&gt;Alright, I copied over my React component pages, commenting Gatsby GraphQL page queries to convert them into Astro content collection syntax... And it's already screaming at me "this blog post has no author"! Quick check: did I forget to copy an author? No, it's indeed missing and not accounted for in Gatsby. Thanks to TypeScript and Astro strict typing, 5 minutes in, I already know of an unaccounted-for edge case.&lt;/p&gt;

&lt;p&gt;After working out how to fetch my posts and display them on my homepage, to match the current blog features, I needed some more grunt work. I needed to create post excerpts.&lt;/p&gt;

&lt;p&gt;Alright, to do so, I simply needed to render the post and take the first K words of the content. Some sanitizing later and it's done.&lt;/p&gt;

&lt;p&gt;I have a few more pages to write: posts by author, by category.&lt;/p&gt;

&lt;p&gt;And it's quite simple to write as well: it's just a JS &lt;code&gt;.filter()&lt;/code&gt; on a list after all. &lt;a href="https://docs.astro.build/en/guides/content-collections/#filtering-collection-queries"&gt;Astro even documents the use case&lt;/a&gt; or better yet, creating filters with an &lt;a href="https://docs.astro.build/en/guides/routing/#nested-pagination"&gt;arbitrary list of tags and paginating those&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Okay, just need to create an RSS feed, there is an Astro integration so it's quite trivial.&lt;/p&gt;

&lt;h3&gt;
  
  
  Migrating data
&lt;/h3&gt;

&lt;p&gt;Okay, so I have a nice blog with 2 posts. I had to adapt those posts a bit to be rendered correctly (edit image path to be relative and not just image slug, think &lt;code&gt;./my_image.png&lt;/code&gt; vs &lt;code&gt;my_image.png&lt;/code&gt; which isn't shocking).&lt;/p&gt;

&lt;p&gt;And that's where shit hit the fan. Astro (in TypeScript strict mode) is so incredibly much stricter than Gatsby. It's an incredible pain to migrate those files, there are over 200 blog posts and if image names can be automated easily (basic glob + regex stuff), some changes need to be dealt with by hand.&lt;/p&gt;

&lt;p&gt;A ton of posts are simply referencing missing images. A quick check on the live blog allows me to verify how Gatsby deals with it. It's simply a broken image, not the best default. Having a "missing image error" blocking the build is a pain when you are migrating all those posts but so incredibly useful to avoid making a typo mistake when contributing.&lt;/p&gt;

&lt;p&gt;Some posts are nearly completely opting out of markdown and using a ton of HTML markup to create custom layouts. With Astro, those could just have used MDX, but I'm faced with a migration issue. I need to keep those posts looking similar, but the rework is quite tedious as MDX is &lt;a href="https://mdxjs.com/docs/troubleshooting-mdx/#problems-writing-mdx"&gt;quite a strict flavor of markdown&lt;/a&gt;. &lt;a href="https://marketplace.visualstudio.com/items?itemName=unifiedjs.vscode-mdx"&gt;I highly recommend using the VSCode MDX extension which helps quite a bit in detecting broken syntax&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;My recommendation is to do those changes using a script. When you have 200+ posts to change, doing this by hand is tedious and error-prone. If a new post is published, you need to import it as well. It's a bit longer to set up first but having a script that does every modification you need directly allows you to check your data migration and play it easily on the latest posts when releasing the migration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Post data migration nice to have
&lt;/h3&gt;

&lt;p&gt;After migrating the data, I still needed to address a few features that I didn't implement yet:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Search: Using our old search was not possible nor wanted and I found a great static page search in &lt;a href="https://pagefind.app/"&gt;Pagefind&lt;/a&gt;. There is even a simple Astro integration to avoid boilerplate code. The search is both faster than before, accessible through the shortcut &lt;code&gt;/&lt;/code&gt; and has a nice excerpt as well as a picture for each result. Nice win!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;By default, there is no React.js on the client, see results for the impact, but it's clearly a better golden path for static sites. I even chose to only keep JSX as Astro components to opt-in to a very light &lt;a href="https://alpinejs.dev/"&gt;Alpine.js&lt;/a&gt; client-side library for light interactivity like the search/header.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I also needed to keep the infinite scroll on the homepage, authors, and categories pages which allow for scrolling all 200 posts as paginated pages of about 10 posts. To do so, I used partial HTML render routes that I render at build time with Astro (like any other page) and used &lt;a href="https://htmx.org/"&gt;HTMX&lt;/a&gt; to fetch pages on scroll. You can try it by scrolling on the &lt;a href="https://blog.theodo.com/"&gt;blog homepage&lt;/a&gt; to see requests going out when scrolling to load the next page and inject it into the current document.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Result of migrating from Gatsby to Astro
&lt;/h2&gt;

&lt;p&gt;My goal with this migration was to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;✅ Simplify features contribution to the blog: Using basic JS/JSX helps in driving down complexity, TypeScript helps in avoiding edge cases.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ At least keep the user experience on par: The blog performance on Lighthouse increased from 69 to a comfortable 99 (migration to Astro + using &lt;a href="https://partytown.builder.io/"&gt;Partytown&lt;/a&gt; for analytics) and a few features/ugly content flash were added/fixed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ Improve developer experience when contributing articles: All posts are rendered in development, page live update is a lot faster on content change, TypeScript helps with typos to avoid missing images.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What I'm less proud of and might have been too much of an experiment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using Alpine.js/HTMX:❓ Both are quite simple tools but especially HTMX requires thinking a bit differently about how to update frontend UI. This might make the contribution a bit harder. However, it needs to be weighed against the amount of logic that would have been a React.js hook + component to create such an infinite scrolling with the corresponding REST API routes.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>astro</category>
      <category>gatsby</category>
      <category>performance</category>
    </item>
    <item>
      <title>How to generate Typescript interfaces from your Spring Boot backend</title>
      <dc:creator>jeremiec</dc:creator>
      <pubDate>Tue, 09 Apr 2024 12:02:00 +0000</pubDate>
      <link>https://forem.com/theodo/how-to-generate-typescript-interfaces-from-your-spring-boot-backend-kfk</link>
      <guid>https://forem.com/theodo/how-to-generate-typescript-interfaces-from-your-spring-boot-backend-kfk</guid>
      <description>&lt;p&gt;Starting a full stack project with Spring Boot and a modern frontend framework like React in Typescript, you rapidly fall into the issue of defining your interfaces twice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Once on Spring Boot side where you create your response/request &lt;a href="https://en.wikipedia.org/wiki/Data_transfer_object" rel="noopener noreferrer"&gt;DTO&lt;/a&gt; for your controllers,&lt;/li&gt;
&lt;li&gt;and again on the frontend where you have to write those interfaces again in Typescript this time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not only is this poor developer experience, leading to a loss in productivity, it simply leads to bugs as you can easily forget to upload your interfaces on one side.&lt;/p&gt;

&lt;p&gt;Great news! Avoiding this pitfall is actually possible and easy ?! with a quick setup, which is the topic of the following guide, let's go!&lt;/p&gt;

&lt;h2&gt;
  
  
  How can we generate Typescript interfaces from our Spring Boot backend
&lt;/h2&gt;

&lt;p&gt;Advancement in code generation and support of standards like Swagger/Open API to describe REST APIs allow for effective code generation. Meaning we only need to set up a proper Swagger on our backend api in Spring Boot to benefit from interface generation for all our interfaces.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcvbz4431jida4ku0ygx4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcvbz4431jida4ku0ygx4.png" alt="Code generation flow: a Spring Boot api export a Swagger which is read by our code generation tool, Orval. Orval then generate all our frontend interfaces in Typescript." width="686" height="348"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  In practice
&lt;/h2&gt;

&lt;p&gt;We will need to expose a Swagger on Spring Boot application and then to configure our code generation tool to target it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up SpringDoc
&lt;/h3&gt;

&lt;p&gt;To generate our Swagger we will use SpringDoc as SpringFox is outdated and less maintained. (&lt;a href="https://springdoc.org/migrating-from-springfox.html" rel="noopener noreferrer"&gt;You can even relatively easily migrate from SpringFox to SpringDoc&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Setting up SpringDoc with Maven is classic, &lt;a href="https://mvnrepository.com/artifact/org.springdoc/springdoc-openapi-ui" rel="noopener noreferrer"&gt;get the latest version&lt;/a&gt;, add it to your pom:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Swagger --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springdoc&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;springdoc-openapi-ui&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.6.9&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are using some sort of authentication, you will need to allow unauthenticated access to the Swagger, eg:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ... rest of your auth&lt;/span&gt;
 &lt;span class="nd"&gt;@Override&lt;/span&gt;
  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WebSecurity&lt;/span&gt; &lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Allow Swagger to be accessed without authentication&lt;/span&gt;
    &lt;span class="n"&gt;web&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ignoring&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;antMatchers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api-docs"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;antMatchers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api-docs/swagger-config"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;antMatchers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/swagger-ui.html"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;antMatchers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/swagger-ui/**"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which you can limit to dev environment if you are not comfortable allowing your backend users to discover your API endpoints using Swagger, to do so use profiles/environment variables as &lt;a href="https://springdoc.org/#disabling-the-springdoc-openapi-endpoints" rel="noopener noreferrer"&gt;you can easily disable SpringDoc&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now start or restart your Spring Boot application, you should be able to get your Swagger (JSON) on &lt;code&gt;http://server:port/context-path/v3/api-docs&lt;/code&gt;, for the application created for this guide it's: &lt;code&gt;http://localhost:8080/guide/v3/api-docs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;One additional step is to configure your Swagger to regroup enums usage definition (meaning when you use an enum in two DTO you will expose only one enum definition in Swagger instead of duplicating the definition):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OpenApiConfig&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// use Reusable Enums for Swagger generation:&lt;/span&gt;
    &lt;span class="c1"&gt;// see https://springdoc.org/#how-can-i-apply-enumasref-true-to-all-enums&lt;/span&gt;
    &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;swagger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;v3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;core&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;jackson&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ModelResolver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;enumsAsRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// ... you can also describe your api bellow&lt;/span&gt;
  &lt;span class="nd"&gt;@Bean&lt;/span&gt;
  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;OpenAPI&lt;/span&gt; &lt;span class="nf"&gt;openAPI&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OpenAPI&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Info&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"My shiny API"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"My shiny API description"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"v0.0.1"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;externalDocs&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ExternalDocumentation&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Documentation"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://my.shiny.api.doc/"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
          &lt;span class="c1"&gt;// can simply be a link to a README&lt;/span&gt;
      &lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This setup is enough for code generation, however I recommend to dive deeper into SpringDoc, it also offers you an UI &lt;code&gt;http://server:port/context-path/swagger-ui.html&lt;/code&gt; which can be an excellent way to document your api for your clients.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up Orval
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://orval.dev/" rel="noopener noreferrer"&gt;Orval&lt;/a&gt; is a recent code generator that propose code generation up to API client generation with the tool of your choice: fetch, Axios, React Query, SWR. For this guide, I will cover interface generation only, however if you are starting a project, I highly recommend setting up client generation as well.&lt;/p&gt;

&lt;p&gt;First install Orval as a dev dependency on your project:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;then create an &lt;code&gt;orval.config.ts&lt;/code&gt; file in your frontend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;orval&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="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;evo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tags&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;schemas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;model/api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// enable/disable test mock generation&lt;/span&gt;
      &lt;span class="c1"&gt;// I recommend enabling this option if you generate an api client&lt;/span&gt;
      &lt;span class="na"&gt;prettier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// recommended if you use prettier&lt;/span&gt;
      &lt;span class="na"&gt;clean&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// recreate the whole folder (avoid outdated files)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// use your own Swagger url: http://server:port/context-path/v3/api-docs&lt;/span&gt;
      &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:8080/guide/v3/api-docs&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once your backend is running, you can generate your interface running &lt;code&gt;npx orval --config ./orval.config.ts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I recommend setting up a script in package.json:&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;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"gen: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;"orval --config ./orval.config.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="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;That's it! Now you only need to launch this command to update your frontend interface when you modify your backend, no more duplication 🎉&lt;/p&gt;

&lt;h2&gt;
  
  
  Going further
&lt;/h2&gt;

&lt;p&gt;You can go a bit further as mentioned in passing in this guide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SpringDoc provides a great UI to self document your API, and you can customize the Swagger generated for your clients,&lt;/li&gt;
&lt;li&gt;Orval has many features including client generation, creating mock for your API, I &lt;em&gt;highly&lt;/em&gt; recommend looking into it,&lt;/li&gt;
&lt;li&gt;the next step would be to automate client side type generation in CI to test that every pull request has up to date types.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>typescript</category>
      <category>springboot</category>
      <category>typesafe</category>
      <category>devex</category>
    </item>
    <item>
      <title>How to beautify java code reliably</title>
      <dc:creator>jeremiec</dc:creator>
      <pubDate>Wed, 07 Feb 2024 13:50:00 +0000</pubDate>
      <link>https://forem.com/theodo/how-to-beautify-java-code-reliably-1886</link>
      <guid>https://forem.com/theodo/how-to-beautify-java-code-reliably-1886</guid>
      <description>&lt;p&gt;I recently had to set up code formatting on a spring boot java application. Auto code formatting is important to avoiding useless diffs in source files, reducing noise in code review, allowing reviewers to focus on what matters.&lt;/p&gt;

&lt;p&gt;Ideally we want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automatic formatting on file save in IDE's (vscode, IntelliJ)&lt;/li&gt;
&lt;li&gt;Capacity to run in CI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wanted to integrate well with vscode and after trying some solution like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;java autoformat, which I couldn't run in CI and that was not really portable between IDEs&lt;/li&gt;
&lt;li&gt;maven auto formatting plugin (didn't run on save easily in IDEs)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I ended up, from a friend suggestion, looking into a &lt;a href="https://github.com/jhipster/prettier-java" rel="noopener noreferrer"&gt;java prettier plugin&lt;/a&gt;. Prettier allowed for an easy integration with most Web IDE, can be configured to format on save for vscode and is trivial to run in CI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing prettier for java
&lt;/h2&gt;

&lt;p&gt;Let's start with the setup in command line:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install nodejs: (&lt;a href="https://github.com/Schniz/fnm" rel="noopener noreferrer"&gt;I highly recommend using a node version manager like fnm&lt;/a&gt;) and to install a recent node version (&lt;a href="https://nodejs.org/en/about/releases/" rel="noopener noreferrer"&gt;current long term support is 16+&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Add a &lt;code&gt;package.json&lt;/code&gt; file at the root of your project and add prettier and the java plugin:
&lt;/li&gt;
&lt;/ul&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;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"api"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.0.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;"private"&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;"devDependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"prettier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.5.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;"prettier-plugin-java"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.6.1"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"engines"&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;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"16.x"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prettier --write &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;{,**/}*.{java,yml,yaml,md,json}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&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;"check-format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prettier --check &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;{,**/}*.{java,yml,yaml,md,json}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&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;"lint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npm run check-format"&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;ul&gt;
&lt;li&gt;Install the required node_module: &lt;code&gt;npm install&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💡 Don't forget to add &lt;code&gt;node_modules&lt;/code&gt; to your &lt;code&gt;.gitignore&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;💡 &lt;a href="https://prettier.io/docs/en/ignore.html" rel="noopener noreferrer"&gt;I highly recommend adding a &lt;code&gt;.prettierignore&lt;/code&gt; that list autogenerated files&lt;/a&gt; to avoid formatting those, eg:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node_modules
target
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can now format java code running &lt;code&gt;npm run format&lt;/code&gt; 🚀&lt;/p&gt;

&lt;p&gt;💡 Do not forget to add node as a dependency for your project installation and to run &lt;code&gt;npm ci&lt;/code&gt; in your project installation script for new developers.&lt;/p&gt;

&lt;p&gt;We now want to improve developer experience by integrating with IDEs, running the formatter in CLI being impractical.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monorepo
&lt;/h3&gt;

&lt;p&gt;If you are using a monorepo, I highly recommend setting up prettier in the root of your monorepo instead of setting up prettier in each sub directory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrating with IDEs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  vscode
&lt;/h3&gt;

&lt;p&gt;To set up prettier in vscode: add the prettier for java extension to recommended vscode extensions for the project, to do so, you can simply add the id of the extension to: &lt;code&gt;.vscode/extensions.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;{
  "recommendations": [
    "pivotal.vscode-boot-dev-pack",
    "vscjava.vscode-java-pack",
    "redhat.vscode-xml",
    "sonarsource.sonarlint-vscode",
    "gabrielbb.vscode-lombok",
    "dbaeumer.vscode-eslint",
&lt;span class="gi"&gt;+   "esbenp.prettier-vscode", // the classic prettier extension
&lt;/span&gt;    "shengchen.vscode-checkstyle",
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then set up this extension as the default linter for java file in &lt;code&gt;.vscode/settings.json&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;{
// rest of your configuration
// automatic format on save, I recommend the save on focus lost option as well
&lt;span class="gi"&gt;+   "editor.formatOnSave": true,
+   "[java]": {
+       "editor.defaultFormatter": "esbenp.prettier-vscode"
+   }
&lt;/span&gt;}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Already we can easily format java code on save:&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flvsxnqkgfr1l5z6mphgj.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flvsxnqkgfr1l5z6mphgj.gif" alt="Formatting java code on save" width="428" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I &lt;strong&gt;highly&lt;/strong&gt; recommend checking in the &lt;code&gt;.vscode&lt;/code&gt; folder in your versioning tool (most likely git) to share this configuration with all developers.&lt;/p&gt;
&lt;h3&gt;
  
  
  IntelliJ
&lt;/h3&gt;

&lt;p&gt;To integrate with prettier java formatter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;First install the &lt;a href="https://www.jetbrains.com/help/idea/using-file-watchers.html#ws_file_watchers_bedore_you_start" rel="noopener noreferrer"&gt;File Watchers IntelliJ plugin&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Then configure the plugin &lt;code&gt;Tools/File Watchers&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Name: Prettier-java
File type: Java
Program:  full path to `.bin/prettier` eg: `node_modules/.bin/prettier`
Arguments: --write $FilePathRelativeToProjectRoot$
Output paths to refresh: $FilePathRelativeToProjectRoot
Auto-save edited files to trigger the watcher: check it
Trigger the watcher on external changes: check it
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://prettier.io/docs/en/webstorm.html#running-prettier-on-save-using-file-watcher" rel="noopener noreferrer"&gt;prettier documentation can be found here for IntelliJ integration&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Integrate code formatting check in CI
&lt;/h2&gt;

&lt;p&gt;Now we only need to integrate this check in CI, I'm using gitlab for this example, but the principles can be transposed to any other CI.&lt;/p&gt;

&lt;p&gt;First we need to add a new job:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;

&lt;span class="c1"&gt;# ... rest of the file&lt;/span&gt;

&lt;span class="c1"&gt;# beautify backend files with prettier&lt;/span&gt;
&lt;span class="na"&gt;lint-back&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node:lts-alpine&lt;/span&gt; &lt;span class="c1"&gt;# We use a node image to run prettier&lt;/span&gt;
  &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt; &lt;span class="c1"&gt;# must match an existing stage&lt;/span&gt;
  &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt; &lt;span class="c1"&gt;# none required optimization that explain to gitlab ci that this test has no dependency and can start as soon as possible&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;npm run lint&lt;/span&gt;
&lt;span class="c1"&gt;# ... rest of the file&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;now just push your change, and you are all set:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw43qmbm2l97s0jq1rjyw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw43qmbm2l97s0jq1rjyw.png" alt="CI running lint successfully" width="569" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>productivity</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Whisper to your keyboard: Setting up a speech-to-text button</title>
      <dc:creator>jeremiec</dc:creator>
      <pubDate>Tue, 06 Feb 2024 13:58:00 +0000</pubDate>
      <link>https://forem.com/theodo/whisper-to-your-keyboard-setting-up-a-speech-to-text-button-2pj5</link>
      <guid>https://forem.com/theodo/whisper-to-your-keyboard-setting-up-a-speech-to-text-button-2pj5</guid>
      <description>&lt;p&gt;Long story short: I broke my arm while riding my bike and I can't type. Quite the problem as typing is a key part of my daily life as a software engineer. So I decided to add a speech to text button to my keyboard. Here's how I did it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to transcript speech to text on linux
&lt;/h2&gt;

&lt;p&gt;First, I looked into how to transcript speech to text on linux. I found a few solutions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Whisper.cpp: a way to run the OpenAI whisper model locally&lt;/li&gt;
&lt;li&gt;OpenAI's API: the original Whisper model as a pay as you go API&lt;/li&gt;
&lt;li&gt;Deepgram: a pay as you go service that offers a speech to text API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After testing whisper.cpp on my machine, it was too slow and inaccurate (out of the box), so I decided to use an API and abandoned the idea of running it locally (for now).&lt;/p&gt;

&lt;p&gt;So I registered for an OpenAI account and tried their API:&lt;/p&gt;

&lt;p&gt;(you can download an &lt;a href="https://dpgr.am/bueller.wav" rel="noopener noreferrer"&gt;example recording here&lt;/a&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;FILEPATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"./recording"&lt;/span&gt; &lt;span class="c"&gt;# path to the recording without extension&lt;/span&gt;

curl &lt;span class="nt"&gt;--request&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; https://api.openai.com/v1/audio/transcriptions &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$OPEN_AI_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: multipart/form-data'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--form&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"@&lt;/span&gt;&lt;span class="nv"&gt;$FILEPATH&lt;/span&gt;&lt;span class="s2"&gt;.wav"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--form&lt;/span&gt; &lt;span class="nv"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;whisper-1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--form&lt;/span&gt; &lt;span class="nv"&gt;response_format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;text &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;FILEPATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.txt"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's quite magical, already I could transcript my voice to text.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to record your microphone on linux
&lt;/h2&gt;

&lt;p&gt;This one was a challenge for compatibility and device selection, but basically, what you want to do is record your microphone as a &lt;code&gt;.wav&lt;/code&gt; file and save it. (I could not make mp3 encoding work reliably and it was not required)&lt;/p&gt;

&lt;p&gt;First use &lt;code&gt;arecord&lt;/code&gt; to list your available devices:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;arecord &lt;span class="nt"&gt;-l&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you should be able to test which input device is your microphone by recording a few samples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;FILEPATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"./recording"&lt;/span&gt;
&lt;span class="nv"&gt;AUDIO_INPUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"hw:0,0"&lt;/span&gt; &lt;span class="c"&gt;# your microphone device, test with a few devices to find the right one&lt;/span&gt;
arecord &lt;span class="nt"&gt;--device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AUDIO_INPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FILEPATH&lt;/span&gt;&lt;span class="s2"&gt;.wav"&lt;/span&gt; &lt;span class="nt"&gt;--duration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to write the text file emulating a keyboard
&lt;/h2&gt;

&lt;p&gt;This one is quite easy, I used &lt;code&gt;xdotool&lt;/code&gt; to emulate a keyboard and write the text file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;FILEPATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"./recording"&lt;/span&gt;
perl &lt;span class="nt"&gt;-pi&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'chomp if eof'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FILEPATH&lt;/span&gt;&lt;span class="s2"&gt;.txt"&lt;/span&gt; &lt;span class="c"&gt;# remove trailing newline if any to avoid sending an extra newline keypress&lt;/span&gt;
xdotool &lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="nt"&gt;--clearmodifiers&lt;/span&gt; &lt;span class="nt"&gt;--file&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FILEPATH&lt;/span&gt;&lt;span class="s2"&gt;.txt"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Putting it all together: a button to transcript speech to text
&lt;/h2&gt;

&lt;p&gt;Now that we have all the pieces, we can put it all together in a script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# usage: exec ./voice-typing.sh twice to start and stop recording&lt;/span&gt;
&lt;span class="c"&gt;# Dependencies: curl, jq, arecord, xdotool, killall&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail
&lt;span class="nv"&gt;IFS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;$'&lt;/span&gt;&lt;span class="se"&gt;\n\t&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;

&lt;span class="c"&gt;# Configuration&lt;/span&gt;
&lt;span class="nb"&gt;readonly &lt;/span&gt;&lt;span class="nv"&gt;PID_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/.recordpid"&lt;/span&gt;
&lt;span class="nb"&gt;readonly &lt;/span&gt;&lt;span class="nv"&gt;FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/.voice-type/recording"&lt;/span&gt;
&lt;span class="nb"&gt;readonly &lt;/span&gt;&lt;span class="nv"&gt;MAX_DURATION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;15
&lt;span class="nb"&gt;readonly &lt;/span&gt;&lt;span class="nv"&gt;AUDIO_INPUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'hw:0,0'&lt;/span&gt; &lt;span class="c"&gt;# Use `arecord -l` to list available devices&lt;/span&gt;

start_recording&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Starting new recording..."&lt;/span&gt;
  &lt;span class="nb"&gt;nohup &lt;/span&gt;arecord &lt;span class="nt"&gt;--device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AUDIO_INPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FILE&lt;/span&gt;&lt;span class="s2"&gt;.wav"&lt;/span&gt; &lt;span class="nt"&gt;--duration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MAX_DURATION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &amp;amp;&amp;gt;/dev/null &amp;amp;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$!&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PID_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

stop_recording&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Stopping recording..."&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PID_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;local &lt;/span&gt;pid
    &lt;span class="nv"&gt;pid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&amp;lt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PID_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$pid&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;wait&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$pid&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; killall &lt;span class="nt"&gt;-w&lt;/span&gt; arecord
    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PID_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;0
  &lt;span class="k"&gt;fi
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"No recording process found."&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt;

write_transcript&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  perl &lt;span class="nt"&gt;-pi&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'chomp if eof'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FILE&lt;/span&gt;&lt;span class="s2"&gt;.txt"&lt;/span&gt;
  xdotool &lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="nt"&gt;--clearmodifiers&lt;/span&gt; &lt;span class="nt"&gt;--file&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FILE&lt;/span&gt;&lt;span class="s2"&gt;.txt"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

transcribe_with_openai&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  curl &lt;span class="nt"&gt;--silent&lt;/span&gt; &lt;span class="nt"&gt;--fail&lt;/span&gt; &lt;span class="nt"&gt;--request&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--url&lt;/span&gt; https://api.openai.com/v1/audio/transcriptions &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$OPEN_AI_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: multipart/form-data'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--form&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"@&lt;/span&gt;&lt;span class="nv"&gt;$FILE&lt;/span&gt;&lt;span class="s2"&gt;.wav"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--form&lt;/span&gt; &lt;span class="nv"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;whisper-1 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--form&lt;/span&gt; &lt;span class="nv"&gt;response_format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;text &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;FILE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.txt"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

main&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PID_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;stop_recording
    transcribe_with_openai
    write_transcript
  &lt;span class="k"&gt;else
    &lt;/span&gt;start_recording
  &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script needs to be run twice to start and stop recording. It will then transcript the recording and write the text to the current window.&lt;/p&gt;

&lt;p&gt;To trigger it with a button, simply add a keyboard shortcut to run the script in your keyboard configurations settings on your linux distribution.&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu59zkibtjy1bnf4fhogo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu59zkibtjy1bnf4fhogo.png" alt="example keyboard shortcut configuration" width="667" height="933"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion and next steps
&lt;/h2&gt;

&lt;p&gt;It was a fun little project to do, and it's quite useful to be able to type with your voice. I'm not sure I'll keep using it after my arm heals, but it's a nice option to have.&lt;/p&gt;

&lt;p&gt;Since using whisper was a little slow, I tried and switched to Deepgram for faster (and sometimes more accurate) transcriptions. I published the &lt;a href="https://github.com/Jeremie-Chauvel/linux-voice-type" rel="noopener noreferrer"&gt;complete script on github, you can find it here&lt;/a&gt;, it checks for requirements and handles errors a little more gracefully.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Effective nodejs version management for the busy developer</title>
      <dc:creator>jeremiec</dc:creator>
      <pubDate>Sun, 04 Feb 2024 12:56:50 +0000</pubDate>
      <link>https://forem.com/theodo/effective-nodejs-version-management-for-the-busy-developer-40fn</link>
      <guid>https://forem.com/theodo/effective-nodejs-version-management-for-the-busy-developer-40fn</guid>
      <description>&lt;p&gt;I highly recommend setting up nodejs with a version manager, &lt;a href="https://github.com/nvm-sh/nvm" rel="noopener noreferrer"&gt;nvm&lt;/a&gt; was and still is a popular option, however, I now recommend and have been using &lt;a href="https://github.com/Schniz/fnm" rel="noopener noreferrer"&gt;fnm&lt;/a&gt;, a simpler and faster alternative to manage my nodejs versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install a nodejs version manager: FNM with automatic version switching
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/Schniz/fnm#installation" rel="noopener noreferrer"&gt;To install fnm, follow the installation steps&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I highly recommend installing the autocompletion, if using zshrc, add this in your &lt;code&gt;~/.zshrc&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# if fnm is installed, add to path and load autocomplete&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; ~/.fnm &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;path+&lt;span class="o"&gt;=&lt;/span&gt;~/.fnm
  &lt;span class="c"&gt;# setup env and allow for automatic node version change when directories contains `.node-version` or `.nvmrc`&lt;/span&gt;
  &lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;fnm &lt;span class="nb"&gt;env&lt;/span&gt; &lt;span class="nt"&gt;--shell&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;zsh &lt;span class="nt"&gt;--use-on-cd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="c"&gt;# shell completion&lt;/span&gt;
  &lt;span class="nb"&gt;source&lt;/span&gt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;fnm completions &lt;span class="nt"&gt;--shell&lt;/span&gt; zsh&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null 2&amp;gt;&amp;amp;1
  compdef _fnm fnm
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart your shell to take those changes into account: &lt;code&gt;exec $SHELL -l&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Use a nodejs version
&lt;/h2&gt;

&lt;p&gt;Then you can easily install and switch node versions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;span class="c"&gt;# install a version&lt;/span&gt;
fnm &lt;span class="nb"&gt;install &lt;/span&gt;lts/iron
&lt;span class="c"&gt;# switch to version&lt;/span&gt;
fnm use lts/iron
&lt;span class="c"&gt;# you can switch to a set version by number as well (eg. v20.9.0)&lt;/span&gt;
node &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi9ghm981w1ih3orslk6e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi9ghm981w1ih3orslk6e.png" alt="change node version with fnm use, automatically switch on cd and install new node version with fnm install" width="655" height="625"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Project setup to promote the correct nodejs version
&lt;/h2&gt;

&lt;p&gt;When using node in a project, I recommend:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Setting up a &lt;code&gt;.nvmrc&lt;/code&gt; file at the root of your project to declare the used nodejs version:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lts/iron
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Using the same version in your runtime, CI, for example with docker:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:iron-alpine&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;In your project package.json, I recommend using the &lt;code&gt;engines&lt;/code&gt; field to declare the nodejs version:
&lt;/li&gt;
&lt;/ul&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;"engines"&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;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;=20"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"pnpm"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;=8"&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;as well as using engine-strict in your &lt;code&gt;.npmrc&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;engine-strict=true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another option is using a preinstall hook for pnpm.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus using pnpm as a package manager
&lt;/h2&gt;

&lt;p&gt;I also recommend using &lt;a href="https://pnpm.io/" rel="noopener noreferrer"&gt;pnpm&lt;/a&gt; as a package manager, it's faster and more efficient than npm or yarn with great capabilities concerning monorepo setup. On recent nodejs versions (v16.13+), you can install it easily with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;corepack &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; corepack prepare pnpm@latest &lt;span class="nt"&gt;--activate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;use engines to enforce usage of pnpm or a preinstall hook:&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;"engines"&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;"pnpm"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;gt;=8"&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;or&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;scripts:&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;"preinstall"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx only-allow pnpm"&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;



</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
