<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <title>CodeMade Blog</title>
  <subtitle>Free software &amp; tech musings</subtitle>
  <link href="https://codemade.net/atom.xml" rel="self" />
  <link href="https://codemade.net/blog/" />
  <updated>2026-04-02T00:00:00Z</updated>
  <id>https://codemade.net/blog/</id>
  <author>
    <name>Loris Bognanni</name>
  </author>
  <entry>
    <title>It&#39;s getting hard to justify app stores</title>
    <link href="https://codemade.net/blog/appstores-are-in-trouble/" />
    <updated>2026-04-02T00:00:00Z</updated>
    <id>https://codemade.net/blog/appstores-are-in-trouble/</id>
    <content type="html">&lt;p&gt;Google&#39;s announcement of the &lt;a href=&quot;https://android-developers.googleblog.com/2026/03/android-developer-verification-rolling-out-to-all-developers.html&quot;&gt;new developer verification process for android developers&lt;/a&gt; and especially the incredibly cumbersome &amp;quot;&lt;a href=&quot;https://android-developers.googleblog.com/2026/03/android-developer-verification.html&quot;&gt;advanced flow&lt;/a&gt;&amp;quot; required to sideload unverified apps left a bad taste in my mouth.&lt;/p&gt;
&lt;p&gt;Here&#39;s why.&lt;/p&gt;
&lt;h3&gt;App stores are yucky&lt;/h3&gt;
&lt;p&gt;When was the last time you thought you&#39;d like to open the app store and see what&#39;s new? Me neither! The only reason to do so is because you already know what you&#39;re looking for, and even then you&#39;ll spend some quality time trying to find it among the sea of adware and sketchy knockoffs: because of the economics of developing apps,  most apps are filled with ads, trackers, and upsells.&lt;/p&gt;
&lt;p&gt;It&#39;s a race to the bottom, and the only way to win is to enshittify your app as much as possible.&lt;/p&gt;
&lt;h2&gt;And then AI coding came along&lt;/h2&gt;
&lt;p&gt;I built two small apps during the past weekend that made me reconsider the whole idea of needing app stores.&lt;/p&gt;
&lt;h3&gt;Hacker news reader&lt;/h3&gt;
&lt;p&gt;My favourite hacker news reader app, &lt;a href=&quot;https://github.com/hidroh/materialistic&quot;&gt;Materialistic&lt;/a&gt; hasn&#39;t been updated in ~ 3 years. It&#39;s so old that &lt;a href=&quot;https://github.com/hidroh/materialistic/issues/1464&quot;&gt;the Play Store doesn&#39;t even have it anymore&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I read HN in a specific way: I want to see the top posts of the day, sorted by number of comments, and I want to read the comments. Sometimes I&#39;ll navigate to the actual article. That&#39;s it.&lt;/p&gt;
&lt;p&gt;I asked Claude to build me a very simple HN reader that does just that, and in a few minutes it materialized on my screen.&lt;/p&gt;
&lt;p&gt;It&#39;s a PWA so it doesn&#39;t even need to be sideloaded, I just have to open the URL and add it to my home screen. It has no ads, no trackers, and it does exactly what I want it to do. It caches requests so it works offline, and it&#39;s blazing fast on cellular networks.&lt;/p&gt;
&lt;p&gt;It&#39;s hosted at &lt;a href=&quot;https://hnws.app&quot;&gt;https://hnws.app&lt;/a&gt; if you want to check it out.&lt;/p&gt;
&lt;p&gt;(I won&#39;t know, because I didn&#39;t add any analytics to it!)&lt;/p&gt;
&lt;h3&gt;Background switcher&lt;/h3&gt;
&lt;p&gt;There&#39;s another app that I&#39;ve been using for years, &lt;a href=&quot;https://github.com/muzei/muzei/&quot;&gt;Muzei&lt;/a&gt;. It&#39;s by far the best wallpaper switcher app.
The killer feature is that it will blur and dim the wallpaper so you can see the icons clearly, and you can reveal the unblurred version with a double tap.&lt;/p&gt;
&lt;p&gt;The only problem I have with it is that most of my wallpapers are landscape, and so they are always centered and cropped, which means that the most interesting part of the image is sometimes cut off.&lt;/p&gt;
&lt;p&gt;Again, my use for it is very specific: I only use local images, and I want the app to automatically switch to the next image in the folder every day. That&#39;s it.&lt;/p&gt;
&lt;p&gt;Claude Code built me &lt;a href=&quot;https://github.com/LBognanni/Localzei/&quot;&gt;LocalZei&lt;/a&gt;, a full fat Android app that does just that, and again, it has no ads, no trackers, and it does exactly what I want it to do.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It took an evening.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;An evening!! While I was watching TV and eating dinner!! Yes it only does a subset of what Muzei does, but it&#39;s &lt;em&gt;my&lt;/em&gt; subset. And it can crop to the most interesting part of the image 😄&lt;/p&gt;
&lt;h2&gt;The age of personal apps is here&lt;/h2&gt;
&lt;p&gt;The friction of building personal apps has always been so high that as users we accepted the ads, the trackers, the dark patterns, and the constant upsells.&lt;/p&gt;
&lt;p&gt;The friction is just not there anymore. You can build a personal app that has the exact subset of features you want, that is clean, fast, and respects your privacy, in a matter of hours.&lt;/p&gt;
&lt;h2&gt;But the enshittifiers are fighting back&lt;/h2&gt;
&lt;p&gt;And this is exactly what Google&#39;s new verification process is designed to prevent: they are desperately trying to protect their cash cow, in the name of &amp;quot;security&amp;quot; and &amp;quot;user experience&amp;quot;.&lt;/p&gt;
&lt;p&gt;Just as we&#39;re so close to our phones being the personal computing devices we always wanted.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Rumors of the death of SaaS have been greatly exaggerated</title>
    <link href="https://codemade.net/blog/death-of-saas/" />
    <updated>2026-02-18T00:00:00Z</updated>
    <id>https://codemade.net/blog/death-of-saas/</id>
    <content type="html">&lt;p&gt;There&#39;s a meme going around in tech and finance circles that goes something like this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Woah I just got Claude Code to build me a custom (insert niche use case here) that works exactly the way I want it! I won&#39;t need to pay for (insert SaaS product here)&#39;s monthly fees ever again! The end of SaaS is near!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here&#39;s why I think this is an extrapolation that makes no sense, especially when thinking of B2B SaaS.&lt;/p&gt;
&lt;p&gt;I&#39;m going to take the weakest position possible here: &lt;strong&gt;vibe coding is not going to replace tasking and planning software like Jira, Asana, Linear etc&lt;/strong&gt;. I&#39;m choosing this specific niche because on paper it&#39;s the most AI-disruptable: it&#39;s software that&#39;s conceptually simple, there are a million TODO list implementations freely sitting on github that have been used as training, and they often need to be customized to a crazy degree to fit the needs of different teams and projects.&lt;/p&gt;
&lt;p&gt;I am confident the points below can apply even more strongly to more complex and specific SaaS products (especially in highly regulated industries like finance, healthcare, legal etc).&lt;/p&gt;
&lt;h2&gt;Nobody wants to maintain your crappy version of Trello&lt;/h2&gt;
&lt;p&gt;You vibe coded a replacement for Trello so that your company can drop the $10/user/month subscription. Congrats! Let&#39;s say you have 100 users (I like round numbers). That&#39;s a saving of $1000/month, or $12,000/year. Seems great, right?&lt;/p&gt;
&lt;p&gt;Except that... how much are &lt;em&gt;you&lt;/em&gt; paid? Let&#39;s say that you&#39;re a software engineer making between $60k and $120k a year. Even if you only spend 10% of your time maintaining this thing, that&#39;s between $6,000 and $12,000 a year of your time spent on it. And remember, that&#39;s time that you&#39;re not spending making money for the company. I won&#39;t even go into the token costs of running an AI agent, as they can range from insignificant to very expensive depending on the complexity of the software you&#39;re building.&lt;/p&gt;
&lt;p&gt;There&#39;s also the &amp;quot;lottery&amp;quot; factor: let&#39;s say you&#39;re offered an exciting position at a new company. Who&#39;s going to maintain the Trello replacement? Your company now has to find someone else to do it, or contract an external agency, or... switch back to Trello and pay the subscription fees again.&lt;/p&gt;
&lt;h2&gt;Free alternatives already exist&lt;/h2&gt;
&lt;p&gt;If your company wanted to replace Trello with a self-hosted alternative, there are already &lt;a href=&quot;https://opensource.com/alternatives/trello&quot;&gt;plenty of free and open source options available&lt;/a&gt;. These products are already maintained by a community of volunteers, and you can even contribute to them if you want to add features or fix bugs.&lt;/p&gt;
&lt;p&gt;So then why don&#39;t all companies just switch to these free alternatives? Because they don&#39;t want to. They don&#39;t want to spend time and resources on maintaining software that is not core to their business. Any time you spend configuring the EC2 instance that runs your self-hosted Trello is time that you&#39;re not spending on the actual product you&#39;re selling to customers. And of course, if something breaks, you&#39;re on the hook for fixing it.&lt;/p&gt;
&lt;h2&gt;Software is a liability&lt;/h2&gt;
&lt;p&gt;We&#39;re assuming that your version of Trello is actually working and doesn&#39;t have any security issues. The worst case scenario would be that &lt;a href=&quot;https://thecyberexpress.com/moltbook-platform-exposes-1-5-mn-api-keys/&quot;&gt;your Firebase database is actually freely available to anyone on the Internet, and all your company&#39;s data is leaked&lt;/a&gt;. And now you&#39;re on the hook for that too. Hope you like HR meetings!&lt;/p&gt;
&lt;p&gt;Had this been a SaaS product, your company could have done one or all of the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sued the vendor for negligence&lt;/li&gt;
&lt;li&gt;Asked for compensation&lt;/li&gt;
&lt;li&gt;Switched to a different vendor&lt;/li&gt;
&lt;li&gt;Ask for a detailed post-mortem and action plan to prevent this from happening again&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Having a &amp;quot;throat to choke&amp;quot; is something that companies &lt;em&gt;really&lt;/em&gt; like.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;SaaS developers can use AI too&lt;/h2&gt;
&lt;p&gt;This should be obvious, but there&#39;s nothing stopping developers at SaaS companies from using AI tools to improve their services. In fact, they&#39;re likely encouraged to do so!&lt;/p&gt;
&lt;p&gt;Whether AI is a net positive or a net negative for working within large, established codebases is still up for debate, but all successful software becomes &amp;quot;legacy&amp;quot; given enough time - even the shiny new vibecoded competitor.&lt;/p&gt;
&lt;p&gt;Some think that the SaaS space will become incredibly crowded with options now that building software is easier than ever. But I expect this will translate into higher expectations for what software should be able to do. In essence, if creating a TODO list app is a one-shot with an LLM, then the expectation for what a task management software should be able to do will be much higher than it is now, and the bar for entry will be raised.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;AI coding raises &lt;em&gt;the floor&lt;/em&gt; for what is considered a &amp;quot;viable&amp;quot; software as a service&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I also expect that some newer, nimbler companies will eventually compete with the established players, but that&#39;s long been the story of software: a great example is Linear coming in and disrupting the Jira monolith by focussing on a better user experience, with what I assume is a tiny fraction of the team at Atlassian.&lt;/p&gt;
&lt;h2&gt;Counterpoint: SaaS companies are overvalued on the stock market&lt;/h2&gt;
&lt;p&gt;This is where the real crux of the issue lies: the stock market has been overvaluing software companies for years now. Given a rational market, the price of a stock should reflect the expected future cash flows of the company. But with software companies, the bet is often that they&#39;ll become unicorns, take over a whole market category, put the competition out of business, and raise prices (AKA the &lt;a href=&quot;https://en.wikipedia.org/wiki/Enshittification&quot;&gt;enshittification&lt;/a&gt; playbook).&lt;/p&gt;
&lt;p&gt;The recent hype about AI being the &amp;quot;next big thing&amp;quot; means that funds are being reallocated away from regular SaaS and into AI companies. This also means that investors &lt;em&gt;need&lt;/em&gt; to push the message that &amp;quot;AI is going to kill SaaS&amp;quot; in order to justify their increasingly outsized bets.&lt;/p&gt;
&lt;p&gt;In that sense yes, AI &lt;em&gt;is&lt;/em&gt; going to kill SaaS. Unprofitable companies that rely on a constant stream of funding are going to have a bad time, and some of them will go out of business. But for companies that are providing actual value to their customers, while staying profitable, I don&#39;t see any reason why they wouldn&#39;t be able to continue doing so, and even thrive in the new AI-powered world.&lt;/p&gt;
&lt;h2&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;AI isn&#39;t going to kill SaaS just yet. But it &lt;em&gt;is&lt;/em&gt; going to make bad SaaS harder to justify.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Building for an audience of one: starting and finishing side projects with AI</title>
    <link href="https://codemade.net/blog/building-for-one/" />
    <updated>2026-02-16T00:00:00Z</updated>
    <id>https://codemade.net/blog/building-for-one/</id>
    <content type="html">&lt;p&gt;&lt;a href=&quot;https://github.com/LBognanni/fasttab&quot;&gt;⚡️ FastTab&lt;/a&gt; solves a very specific problem: the &amp;quot;Gallery&amp;quot; view of the built-in task switcher in the Plasma desktop environment is slightly too slow for my liking on X11. Sometimes it will take up to a second to open, which is way too long for a feature that I use constantly.&lt;/p&gt;
&lt;p&gt;FastTab is a custom task switcher that is built in Zig, uses OpenGL for rendering, and is designed to run as a daemon so that it can respond to keyboard shortcuts instantly.&lt;/p&gt;
&lt;h2&gt;I would have never built this without AI&lt;/h2&gt;
&lt;p&gt;It&#39;s very likely that this is a problem with an audience of one: users who are still on X11, like the gallery switcher, have many windows open, and care about performance.&lt;/p&gt;
&lt;p&gt;In the past I would have probably just accepted the performance tradeoff and moved on, but we&#39;re living in &lt;em&gt;the future&lt;/em&gt; now! Why not just ask Claude to build it for me? As long as the vision is good, and the code doesn&#39;t suck, it should be good enough, right?&lt;/p&gt;
&lt;p&gt;And so I did: with zero experience with Zig or X11 internals, I got a working prototype in a few days, and then iterated on it until I was happy with the results.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;AI tools enable you to build things that you would have never built otherwise. When you just want &amp;quot;the thing&amp;quot; rather than the process of building the thing, coding agents can be a game changer.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;It all starts with a conversation&lt;/h2&gt;
&lt;p&gt;I was just irritated enough by the slight delay of the task switcher that I asked Claude for tips on how to make it faster. Sadly, there was no quick fix, I had already zeroed out all the animations and delays in the configuration, and the only alternative would have been to switch to the &amp;quot;list&amp;quot; or &amp;quot;icons-only&amp;quot; view, which I just don&#39;t like (looking at a window preview is much more efficient than reading titles for my brain).&lt;/p&gt;
&lt;p&gt;Then I just asked: &amp;quot;How difficult would it be to build a task switcher that is specifically for X11 and KDE?&amp;quot; and the conversation pivoted into planning. We went through a few iterations where I steered Claude towards a design that seemed promising.&lt;/p&gt;
&lt;p&gt;The result was a very &lt;strong&gt;detailed specification&lt;/strong&gt; of the application, with a clear vision of how it should work and look like, and a plan for how to implement it, split in &lt;strong&gt;several milestones&lt;/strong&gt; so that I could start with a simple prototype and then evolve it into a full-fledged application.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Start with a conversation, and explore the problem space with the LLM. The idea here is to gather options and ideas. Once you have a clear vision of what you want to build, ask for a detailed specification. Iterate on the spec until you &lt;em&gt;understand it fully&lt;/em&gt; and are happy with it.&lt;/p&gt;
&lt;p&gt;Then break down the spec in a set of milestones.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;A note on writing good specs:&lt;/strong&gt; Claude especially will try to shove as many code snippets as possible into the spec. This is not only a waste of tokens, but it will also make it harder for you (&lt;em&gt;and the coding agent!&lt;/em&gt;) to implement the spec. Pseudocode is fine, and at this stage is actually preferable, since it allows you to focus on the overall architecture and design of the application, without getting bogged down in the details of the implementation.&lt;/p&gt;
&lt;p&gt;Mermaid diagrams are also your friend here, they not only look nicer, but fit in fewer tokens than ASCII diagrams.&lt;/p&gt;
&lt;p&gt;With a solid spec in hand, I was ready to start building. The next question was how to let an AI agent loose on my filesystem without risking my machine.&lt;/p&gt;
&lt;h2&gt;The perfect balance between safe and YOLO&lt;/h2&gt;
&lt;p&gt;Every developer should know that &lt;code&gt;git&lt;/code&gt; is your friend. You can build incrementally, commit when you have something working, and if the LLM messes up, you can always revert to the last working state.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I use the staging area a lot when I have something that is kinda working but not quite there yet, so I can decide whether the next iteration is good enough to make it into a commit, it&#39;s an improvement that gets staged, or it&#39;s garbage to be reverted.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Checking the git diff before committing is also a great way to understand what the LLM is doing, and to catch any potential mistakes before they become a problem.&lt;/p&gt;
&lt;p&gt;Running Claude Code and approving... each... individual... command... is... exhausting. But at the same time, running &lt;code&gt;claude --dangerously-skip-permissions&lt;/code&gt; is gambling with your filesystem.&lt;/p&gt;
&lt;p&gt;The solution is 🐋 containers! I used a heavily customized version of &lt;a href=&quot;https://github.com/frequenz-floss/contai/&quot;&gt;contai&lt;/a&gt;, which is a handy wrapper around Docker that allows you to easily spin up a locked down container that has access to your code folder only. This way if the LLM accidentally runs &lt;code&gt;rm -rf /&lt;/code&gt; or something like that, it will only delete the container&#39;s filesystem, which is ephemeral, and not your actual files. (should it delete the code, you are using git, remember?)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I had to tell the LLM that it&#39;s running inside a container, and it doesn&#39;t have an X11 display or the ability to install system packages, otherwise it would spend a lot of tokens trying to do the impossible&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It worked so well that all my new projects are now developed in a &lt;a href=&quot;https://codemade.net/blog/devcontainers&quot;&gt;dev container&lt;/a&gt; that borrows heavily from the &lt;code&gt;contai&lt;/code&gt; configuration.&lt;/p&gt;
&lt;h2&gt;Trying different tools, and running out of tokens&lt;/h2&gt;
&lt;p&gt;I initially started working on FastTab with &lt;a href=&quot;https://opencode.ai/&quot;&gt;OpenCode&lt;/a&gt; and &lt;a href=&quot;https://github.com/code-yeongyu/oh-my-opencode&quot;&gt;oh-my-opencode&lt;/a&gt;. I really enjoyed OpenCode in other projects and, while skeptical, I hoped that oh-my-opencode would be the powerup that would make the whole experience 100 times better.&lt;/p&gt;
&lt;p&gt;In practice, I found that the multi-agent system was exceptional at consuming tokens, while producing the same results that I could have achieved with a single agent. At around the same time, Anthropic decided that using the Pro plan on OpenCode was a breach of their TOS, so I had to switch back to Claude Code.&lt;/p&gt;
&lt;p&gt;The token limit however remained a real problem: I assume that writing Zig is more taxing to LLMs than writing Python or JavaScript, probably because of the lower level nature of the language and the fact that less training data is available for it. I routinely ran out of juice, and had to either wait for the reset, or switch to the Gemini CLI. Alternating between the two was a bit of a hassle, but overall both Opus 4.5 and Gemini 3 were able to get the job done.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Sidenote: I wonder what&#39;s going to happen when the crazy money runs out and Anthropic, OpenAI &amp;amp; co have to start charging for more than it costs them to run the models. Hopefully by then the open source models will have caught up?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;But regardless of which model or tool I was using, the pattern was always the same: the LLM could get me 80% of the way there, and the last 20% was on me.&lt;/p&gt;
&lt;h2&gt;You still need to know how to code&lt;/h2&gt;
&lt;p&gt;In the case of FastTab, the first version that I got from the multi-agent system was actually working, which was way better than what I could have built in a couple of hours in a language I didn&#39;t know, using libraries I wasn&#39;t familiar with.&lt;/p&gt;
&lt;p&gt;It was also a single 1700 lines long file with most of the code in the &lt;code&gt;main&lt;/code&gt; function, no tests, lots of code duplication, and a lot of extra comments and logging. Refactoring it into a more modular and maintainable state was necessary not only so I could understand it, but so that the LLM could iterate on it without introducing new bugs or breaking existing functionality.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;There&#39;s a reason why many developers call AI assisted coding &amp;quot;a slot machine&amp;quot;. You write your prompt, pull the lever, and get a surprise. Sometimes it&#39;s a fully working thing, sometimes it&#39;s 50% there, and sometimes it&#39;s completely off the mark. Better prompts will increase the chances of getting a good result, but there&#39;s always an element of randomness that you have to be comfortable with.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Asking the right questions also requires coding knowledge. For example, in an early iteration we were shuffling image bytes from BGRA to RGBA. The code worked, but the performance was not great. I asked Claude to use SIMD instructions and the speedup was significant, enough so that we could capture the content of a youtube window playing a video at ~10fps without incinerating my CPU. But &lt;strong&gt;I had to know that SIMD was a thing&lt;/strong&gt;, and that it could be used in this context.&lt;/p&gt;
&lt;p&gt;In the end we ended up scrapping much of that code in favour of &amp;quot;borrowing&amp;quot; the actual texture data from X11, which is much faster, but the point stands: you need to know what questions to ask in order to get the best results.&lt;/p&gt;
&lt;h2&gt;The age of finishing side projects is here?&lt;/h2&gt;
&lt;p&gt;I think I&#39;m finally turning the corner from the doom and gloom of &amp;quot;AI will take our jobs.&amp;quot; AI-assisted coding is still coding: without the taste and knowledge that come with experience, you&#39;ll invariably end up with a half working prototype that breaks in new and exciting ways every time you ask to &amp;quot;pls fix&amp;quot;.&lt;/p&gt;
&lt;p&gt;For my day job, where the codebase dwarfs any LLM&#39;s context window and the stakes are high, getting a coding agent to generate all or even most of the code for a feature is still not something I would subject my users to.&lt;/p&gt;
&lt;p&gt;But side projects have different goals: scratch an itch, maybe learn a thing or two, and get something working before I get bored or realize it&#39;s too much effort.&lt;/p&gt;
&lt;p&gt;I can spitball with Claude about my problem, get a spec, and have a working prototype in a few hours. If I like it enough, I can spend some time polishing it until it&#39;s something I&#39;m happy to share.&lt;/p&gt;
&lt;p&gt;The whole process is short enough that I might actually finish the thing, for a change!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>How to use Deno&#39;s own SQLite module on Alpine Linux</title>
    <link href="https://codemade.net/blog/deno-sqlite-on-alpine/" />
    <updated>2026-01-18T00:00:00Z</updated>
    <id>https://codemade.net/blog/deno-sqlite-on-alpine/</id>
    <content type="html">&lt;p&gt;Deno&#39;s built-in SQLite module is a fantastic way to integrate a lightweight database into your Deno applications.
However, to work its magic, it relies on a native SQLite library that needs to be present in your environment.&lt;/p&gt;
&lt;p&gt;If you&#39;re running one of Ubuntu, MacOS, or Windows, you&#39;re in luck: Deno has you covered with &lt;a href=&quot;https://github.com/denodrivers/sqlite3/releases/&quot;&gt;precompiled binaries for these platforms&lt;/a&gt;. But if you&#39;re using Alpine Linux, things get a bit trickier since Deno doesn&#39;t provide a precompiled SQLite library for this distribution.&lt;/p&gt;
&lt;p&gt;Try to run a Deno application that uses SQLite on Alpine Linux without any additional setup, and you&#39;ll get an error like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3.013 Downloading https://github.com/denodrivers/sqlite3/releases/download/0.13.0/libsqlite3.so
4.094 error: Uncaught (in promise) Error: Failed to load SQLite3 Dynamic Library
4.094   throw new Error(&amp;quot;Failed to load SQLite3 Dynamic Library&amp;quot;, { cause: e });
4.094         ^
4.094     at https://jsr.io/@db/sqlite/0.13.0/src/ffi.ts:642:9
4.094 Caused by: Error: Could not open library: Could not open library: /usr/local/lib/libm.so.6: version `GLIBC_2.38&#39; not found (required by /deno-dir/plug/https/github.com/2d624d1cb94dc52a04dcb942d98592ad72012273e34867346f5dde5d01035183.so)
4.094     at new DynamicLibrary (ext:deno_ffi/00_ffi.js:457:42)
4.094     at Object.dlopen (ext:deno_ffi/00_ffi.js:563:10)
4.094     at dlopen (https://jsr.io/@denosaurs/plug/1.1.0/mod.ts:158:15)
4.094     at eventLoopTick (ext:core/01_core.js:187:7)
4.094     at async https://jsr.io/@db/sqlite/0.13.0/src/ffi.ts:625:7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What this tells us is that Deno attempted to download a precompiled SQLite library that depends on &lt;code&gt;glibc&lt;/code&gt;, the GNU C Library. However, Alpine Linux uses &lt;code&gt;musl&lt;/code&gt; as its standard C library, which is not compatible with the prebuilt sqlite binary.&lt;/p&gt;
&lt;p&gt;Luckily, Deno can use any precompiled SQLite library that matches its expected version.
For Alpine Linux, you can leverage the &lt;code&gt;libsqlite3&lt;/code&gt; package available in the Alpine repositories.&lt;/p&gt;
&lt;p&gt;You can install it using the &lt;code&gt;apk&lt;/code&gt; command:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;apk &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; libsqlite3&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command installs the necessary SQLite library on your Alpine system, however in order for Deno to find and use it, you will need to set the &lt;code&gt;DENO_SQLITE_PATH&lt;/code&gt; environment variable to point to the location of the installed library.&lt;/p&gt;
&lt;p&gt;You can find the library at &lt;code&gt;/usr/lib/libsqlite3.so.0&lt;/code&gt; after installation.
Set the environment variable like this:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;DENO_SQLITE_PATH&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;/usr/lib/libsqlite3.so.0&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;That&#39;s nice and all but I use Docker?&lt;/h2&gt;
&lt;p&gt;Easy peasy! Here&#39;s a simple Dockerfile that sets up a Deno environment on Alpine Linux with SQLite support:&lt;/p&gt;
&lt;pre class=&quot;language-Dockerfile&quot;&gt;&lt;code class=&quot;language-Dockerfile&quot;&gt;# Use the official Deno Alpine image as the base
FROM denoland/deno:alpine-2.6.5

# Expose port 8000 for the application
EXPOSE 8000

# Install system SQLite library
RUN apk add --no-cache sqlite-libs

# Set working directory and permissions
WORKDIR /app
RUN mkdir /app/data &amp;&amp; chown -R deno:deno /app
USER deno

# Copy deno.json and deno.lock files
COPY deno.* .

# Set the DENO_SQLITE_PATH environment variable to point to the installed SQLite library
ENV DENO_SQLITE_PATH=/usr/lib/libsqlite3.so.0

# Pre-install app dependencies
RUN deno install --frozen=true
# Force Deno to load the SQLite module and download any necessary files, ensuring everything is set up correctly
RUN deno eval &quot;import &#39;jsr:@db/sqlite&#39;;&quot;

# Copy application source code
COPY src/ ./src/
# Install any other dependencies
RUN deno install --entrypoint src/main.ts

# Command to run the application (assumes you have a `prod` task in your deno.json)
CMD [&quot;deno&quot;, &quot;run&quot;, &quot;prod&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;Hope this was helpful to you, random internet stranger! It certainly took me a bit of digging to figure this out, so hopefully this saves you some time! 🙃&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Announcing email-webhook</title>
    <link href="https://codemade.net/blog/announcing-email-webhook/" />
    <updated>2025-03-22T00:00:00Z</updated>
    <id>https://codemade.net/blog/announcing-email-webhook/</id>
    <content type="html">&lt;div class=&quot;callout&quot;&gt;
  &lt;p&gt;📨&lt;/p&gt;
  &lt;p&gt;
    &lt;b&gt;TLDR:&lt;/b&gt; 
    I&#39;m excited to announce the launch of &lt;a href=&quot;https://email-webhook.com&quot;&gt;email-webhook&lt;/a&gt;, a service that transforms emails into API calls. Use it to write email automations, process incoming emails, and build email-based workflows.
  &lt;/p&gt;
&lt;/div&gt;
&lt;h2&gt;What is email-webhook?&lt;/h2&gt;
&lt;p&gt;As the name suggests, email-webhook is a service for developers that transforms emails into REST requests to your API. It makes integrating an email based workflow into your applications and services a breeze.&lt;/p&gt;
&lt;p&gt;As opposed to traditional email polling techniques, email-webhook sends a request to your endpoint as soon as an email is received. This way, you can process emails in real-time, without the need for complex polling logic.&lt;/p&gt;
&lt;h3&gt;Here&#39;s how it works:&lt;/h3&gt;
&lt;p&gt;Once you&#39;re signed up, you&#39;ll receive a special web address like &lt;code&gt;you@email-webhook.com&lt;/code&gt;. Any time an email is sent there, email-webhook will send a request to your endpoint.&lt;/p&gt;
&lt;p&gt;You can specify the request method, custom headers, and even sender-based rules. This way, you can easily integrate email processing into your existing systems.&lt;/p&gt;
&lt;h2&gt;Why email-webhook?&lt;/h2&gt;
&lt;p&gt;This started a while ago as I was looking for a way to improve the efficiency of my lovely partner&#39;s business, using AI to automate her most time consuming tasks.&lt;/p&gt;
&lt;p&gt;One such task was improving her response time to various requests coming from her clients. She was receiving a lot of emails and she was spending &lt;strong&gt;a lot&lt;/strong&gt; of time reading and responding to them. Her biggest issue was the &amp;quot;blank screen&amp;quot; problem: she would open an email, read it, and then spend a lot of time thinking about what to do with it.&lt;/p&gt;
&lt;p&gt;I thought that I could help her by building a system that would intelligently propose a draft response based on the content of the email plus some external data sources such as her calendar and her CRM.&lt;/p&gt;
&lt;p&gt;One of the first steps was to find a way to process emails as they arrived. As a web developer, I&#39;m used to working with APIs, so I thought: &amp;quot;Why not build an API that receives emails as input?&amp;quot;&lt;/p&gt;
&lt;p&gt;Of course, the next step was to find a way to get emails into the API! I wanted to avoid polling, so I thought about using webhooks. I started looking for a service that would allow me to receive emails as webhooks, but I couldn&#39;t find anything that suited my needs. Every other option was either too complex, too expensive, or too limited.&lt;/p&gt;
&lt;p&gt;So I decided to build my own service: &lt;a href=&quot;https://email-webhook.com&quot;&gt;email-webhook&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Looking for feedback&lt;/h2&gt;
&lt;p&gt;Email-webhook is still in its early stages: it&#39;s sleek and functional (IMO), but there&#39;s still room for improvement.&lt;/p&gt;
&lt;p&gt;This is why I&#39;m offering it for the low low price of &lt;em&gt;FREE&lt;/em&gt; while I gather feedback and improve the service.&lt;/p&gt;
&lt;p&gt;I don&#39;t ever plan for email-webhook to be an expensive service, but I would like to offer a paid tier in the future with some advanced features to help me cover the costs of running it.&lt;/p&gt;
&lt;h2&gt;On a personal note&lt;/h2&gt;
&lt;p&gt;I had a lot of fun building email-webhook. After so many years of having random ideas and never acting on them, it feels great to finally have something concrete to show for it.&lt;/p&gt;
&lt;p&gt;It was a great chance to learn more about the SMTP protocol, and if you&#39;ve been following my blog, you will probably guess that &lt;a href=&quot;https://codemade.net/blog/a-journey-through-deno-ssr.md&quot;&gt;I used Deno&lt;/a&gt; to build the service 😄.&lt;/p&gt;
&lt;p&gt;I&#39;m excited to see how people will use email-webhook. I&#39;ve already received some great feedback from early users, and I can&#39;t wait to see what they&#39;ll build with it.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>How I deploy my web apps in 2025</title>
    <link href="https://codemade.net/blog/my-docker-setup-in-2025/" />
    <updated>2025-03-15T00:00:00Z</updated>
    <id>https://codemade.net/blog/my-docker-setup-in-2025/</id>
    <content type="html">&lt;p&gt;The beauty of simplicity has become my north star for hosting web applications. Despite working with complex cloud architectures professionally, I&#39;ve chosen a straightforward Docker stack for my personal projects. As a solo maintainer, I need something manageable without breaking the bank.&lt;/p&gt;
&lt;p&gt;Docker hits that sweet spot between complexity and simplicity, providing enough structure without the overhead of enterprise cloud solutions. My personal projects don&#39;t need Kubernetes clusters or managed databases - just reliable, affordable infrastructure.&lt;/p&gt;
&lt;h2&gt;The Old Ways: Docker Compose for the Basics&lt;/h2&gt;
&lt;pre class=&quot;mermaid&quot;&gt;
flowchart LR
web(Web Request)
subgraph **VPS**
  subgraph docker_compose.yml
    rp[Reverse Proxy]
    codemade_app[codemade.net]
    service_x[Wordpress 1]
    service_y[Postgres]
    service_z[Email forwarder]
  end
end
web -- web --&gt; rp --&gt; codemade_app
rp --&gt;service_x --&gt; service_y 
web -- email --&gt; service_z
&lt;/pre&gt;
&lt;p&gt;Initially, my hosting needs were modest - a mail forwarder and a couple of WordPress sites. For this small collection, a simple docker compose setup was adequate. I&#39;d SSH into my server, run commands, and everything would be up and running with minimal fuss.&lt;/p&gt;
&lt;p&gt;This hands-on approach worked perfectly - make a change, connect to the server, pull the latest version, and restart containers. Since updates were infrequent and downtime wasn&#39;t critical, this manual method suited my needs. My docker compose file was straightforward, defining just the necessary services with basic volume mounts and simple network configurations.&lt;/p&gt;
&lt;p&gt;The only &amp;quot;fancy&amp;quot; element was this website, which I&#39;ve since &lt;a href=&quot;https://github.com/LBognanni/codemade-site/blob/master/.github/workflows/deploy.yml&quot;&gt;moved to 11ty and GitHub Pages&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;And then I built a web app&lt;/h2&gt;
&lt;pre class=&quot;mermaid&quot;&gt;
flowchart LR
web(Web Request)
web --&gt; caddy[/Reverse Proxy/]
app
redis
subgraph **VPS**
  caddy
  subgraph Stats Stack
    app[Stats App]
    redis[(Stats DB)]
  end
end
caddy -.-&gt; app
app &lt;-.-&gt; redis
&lt;/pre&gt;
&lt;p&gt;Recently, I built a web application that required a more robust solution. It was time to graduate from docker compose to something more sophisticated.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Docker Swarm&lt;/strong&gt; was the perfect choice as a step up from docker compose without going full Kubernetes-level complexity.&lt;/p&gt;
&lt;p&gt;I first heard about this approach from the &lt;a href=&quot;https://www.youtube.com/watch?v=fuZoxuBiL9o&quot;&gt;DreamsOfCode YouTube channel&lt;/a&gt;, where they called it &lt;code&gt;docker stack&lt;/code&gt;. It&#39;s technically Docker Swarm with one node, offering a pragmatic middle ground between my simple setup and complex orchestration tools.&lt;/p&gt;
&lt;p&gt;The stack deployment files look almost identical to docker compose files, with just a few additional orchestration-specific options. I took my existing compose file, added deployment constraints and update policies, and it was ready - no steep learning curve required.&lt;/p&gt;
&lt;p&gt;I host it on a single but capable &lt;strong&gt;VPS&lt;/strong&gt; on &lt;strong&gt;DigitalOcean&lt;/strong&gt;, which is more than enough for my needs.&lt;/p&gt;
&lt;h3&gt;The Automated Pipeline&lt;/h3&gt;
&lt;p&gt;Here is my current workflow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Push code changes to GitHub&lt;/li&gt;
&lt;li&gt;GitHub Actions builds a container image and publishes it to GitHub Container Registry&lt;/li&gt;
&lt;li&gt;A deployment action (&lt;a href=&quot;https://github.com/marketplace/actions/docker-stack-swarm-deploy-action&quot;&gt;&lt;code&gt;shockhs/docker-stack-deploy@v1.2&lt;/code&gt;&lt;/a&gt;) automatically updates my Swarm stack&lt;/li&gt;
&lt;li&gt;The new version rolls out with zero downtime&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can take a look at a simplified version of my &lt;a href=&quot;https://gist.github.com/LBognanni/484f420eb7dbc5679f071329a952831b&quot;&gt;GitHub Actions workflow&lt;/a&gt; if you&#39;re curious.&lt;/p&gt;
&lt;h3&gt;Handling SSL with Caddy&lt;/h3&gt;
&lt;p&gt;For SSL and routing, I use the excellent &lt;a href=&quot;https://caddyserver.com&quot;&gt;Caddy&lt;/a&gt; as a reverse proxy. It automatically handles certificate generation and renewal from Let&#39;s Encrypt, which is a huge time-saver.&lt;/p&gt;
&lt;p&gt;Caddy runs as a separate standalone container outside the Swarm cluster.
This was a deliberate architectural decision - running the reverse proxy inside the Swarm would mean &lt;a href=&quot;https://github.com/moby/moby/issues/25526&quot;&gt;losing the originating IP addresses of incoming requests&lt;/a&gt; due to how networking works in Swarm mode.&lt;/p&gt;
&lt;p&gt;By keeping Caddy separate, I maintain visibility of client IPs for proper logging and security monitoring.
It also means my reverse proxy isn&#39;t tied to the lifecycle of my applications. It gets its own repo, and its own deployment pipeline.&lt;/p&gt;
&lt;h3&gt;The Results&lt;/h3&gt;
&lt;p&gt;This setup strikes the perfect balance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Automated&lt;/strong&gt;: No manual SSH sessions for routine deployments&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reliable&lt;/strong&gt;: Rolling updates ensure zero-downtime deployments&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Maintainable&lt;/strong&gt;: Everything is defined in code and version controlled&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Affordable&lt;/strong&gt;: More cost-effective than equivalent managed cloud services&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;And then there were more&lt;/h2&gt;
&lt;pre class=&quot;mermaid&quot;&gt;
flowchart LR
web(Web Request)
web --&gt; caddy[/Reverse Proxy/]
app
redis
subgraph **VPS**
  caddy
  subgraph Stats Stack
    app[Stats App]
    redis[(Stats DB)]
  end
  subgraph Games Stack
    gapp[Games App]
    gdb[(Games DB)]
  end
end
caddy -.-&gt; app
app &lt;-.-&gt; redis
caddy -.-&gt; gapp
gapp &lt;-.-&gt; gdb
&lt;/pre&gt;
&lt;p&gt;Docker Swarm excels at scaling to multiple applications. Adding a new web app was straightforward - I created a new stack definition file and deployed it to the same swarm.&lt;/p&gt;
&lt;p&gt;This provides independent deployment lifecycles for each application. I can update, restart, or delete one app without affecting others. Each stack gets its own isolated network by default, though they can communicate with each other if needed through overlay networks.&lt;/p&gt;
&lt;h2&gt;Looking forward&lt;/h2&gt;
&lt;pre class=&quot;mermaid&quot;&gt;
flowchart LR
web(Web Request)
web --&gt; caddy[/Reverse Proxy/]
subgraph VPS4
  subgraph Stats Node / 3
    app4[Stats App]
    redis4[(Stats DB)]
  end
end
subgraph VPS3
  subgraph Stats Node / 2
    app3[Stats App]
    redis3[(Stats DB)]
  end
end
subgraph VPS2
  subgraph Stats Node / 1
    app2[Stats App]
    app21[Stats App]
    app22[Stats App]
    redis2[(Stats DB)]
  end
end
subgraph **VPS 1**
  caddy --&gt; other[other serivces]
end
caddy -.-&gt; app2
app2 -.-&gt; redis2
app21 -.-&gt; redis2
app22 -.-&gt; redis2
caddy -.-&gt; app3
app3 -.-&gt; redis3
caddy -.-&gt; app4
app4 -.-&gt; redis4
style other stroke-dasharray: 5, 5;
style VPS2 fill:#fff4,stroke-dasharray: 5, 5;
style VPS3 fill:#fff4,stroke-dasharray: 5, 5;
style VPS4 fill:#fff4,stroke-dasharray: 5, 5;
&lt;/pre&gt;
&lt;p&gt;Should one of my services become a runaway success, I can move it to its own VPS and scale independently, either by increasing VPS size or adding replicas to the swarm stack. For further growth, adding more VPSs to the same swarm would be the next step.&lt;/p&gt;
&lt;p&gt;But that&#39;s a problem for another day! 😎&lt;/p&gt;
&lt;script defer=&quot;&quot; src=&quot;https://cdn.jsdelivr.net/npm/mermaid@11.4.1/dist/mermaid.min.js&quot;&gt;&lt;/script&gt;
</content>
  </entry>
  <entry>
    <title>Running parameterized tests in Deno</title>
    <link href="https://codemade.net/blog/parameterized-testing-with-deno/" />
    <updated>2025-03-09T00:00:00Z</updated>
    <id>https://codemade.net/blog/parameterized-testing-with-deno/</id>
    <content type="html">&lt;p&gt;When writing tests, it&#39;s common to want to run the same test with different inputs. This is called parameterized testing, and it&#39;s a great way to ensure that your code behaves correctly under different conditions.&lt;/p&gt;
&lt;p&gt;This is usually done by writing a single test function that takes parameters, and then calling that function with different inputs. Here is an example in C# using NUnit:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;TestCase&lt;/span&gt;&lt;span class=&quot;token attribute-arguments&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;TestCase&lt;/span&gt;&lt;span class=&quot;token attribute-arguments&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;TestCase&lt;/span&gt;&lt;span class=&quot;token attribute-arguments&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;TestAdd&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;/span&gt; a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;/span&gt; expected&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; a &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    Assert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;AreEqual&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;expected&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Testing in Deno&lt;/h2&gt;
&lt;p&gt;In Deno, we can take advantage of the integrated testing library by using &lt;code&gt;Deno.test()&lt;/code&gt; to define our tests. We can further split our tests into separate steps, like so:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;Deno&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;When activating the flux capacitor&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; capacitor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;activateFluxCapacitor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  
  t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;It should generate 1.21 gigawatts of power&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; power &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; capacitor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;generatePower&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;assertEquals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;power&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1.21&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;It should travel through time&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; time &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; capacitor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;travelThroughTime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;assertEquals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;time&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1985&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sometimes however we want to test a function with multiple inputs. An easy way to do so would be to iterate over an array of inputs and call the test function for each input. This can be a bit cumbersome, especially if we have a lot of inputs:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;Deno&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;TestAdd&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; testCases &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;expected&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;expected&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;expected&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; input &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; testCases&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; expected &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; input&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; a &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;assertEquals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; expected&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A disadvantage of this approach is that if any of the test cases fail, the test will stop running and we won&#39;t know the results of the remaining test cases. The test runner will also simply mark the whole test as failed, without providing any information about which test case failed!&lt;/p&gt;
&lt;h2&gt;How to run parameterized tests in Deno&lt;/h2&gt;
&lt;p&gt;After searching online for a bit and finding nothing helpful, I decided to take a look at how Deno&#39;s developers write tests.
A quick github search took me to &lt;a href=&quot;https://github.com/denoland/deno/blob/0ef3f6ba887d7aed2d94c8b622563d13bfecda2c/tests/unit/serve_test.ts#L2441&quot;&gt;the tests for Deno&#39;s &lt;code&gt;serve&lt;/code&gt; function&lt;/a&gt;. Here we can see that they use a helper function to generate multiple test cases.&lt;/p&gt;
&lt;p&gt;The trick is that they call &lt;code&gt;Deno.test()&lt;/code&gt; &lt;em&gt;inside&lt;/em&gt; the helper function!&lt;/p&gt;
&lt;p&gt;We can adapt this trick in our tests like so:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;testAdd&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; expected&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  Deno&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;TestAdd(&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;a&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;, &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;b&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; a &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;assertEquals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; expected&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;testAdd&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;testAdd&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;testAdd&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another approach is to use an array and a for loop:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; testCases &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;expected&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;expected&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;expected&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; expected&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; testCases&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  Deno&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;TestAdd(&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;a&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;, &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;b&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; a &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;assertEquals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; expected&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This way, if any of the test cases fail, the test runner will still run the remaining test cases and provide information about which test case failed.&lt;/p&gt;
&lt;p&gt;Additionally, by specifying unique test names in each Deno.test() call, we can easily identify which test case failed!&lt;/p&gt;
&lt;h3&gt;A note on the Visual Studio Code Deno extension&lt;/h3&gt;
&lt;p&gt;At the time of writing, you won&#39;t be able to run individual tests in Visual Studio Code from the test file itself (ie you won&#39;t see a &amp;quot;Run Test&amp;quot; button next to each test).&lt;/p&gt;
&lt;p&gt;You can still run the whole test file by right-clicking on the file and selecting &amp;quot;Run Test&amp;quot; from the context menu.&lt;/p&gt;
&lt;p&gt;Once you&#39;ve done that, the tests will also be visible in the &amp;quot;Testing&amp;quot; tab, where you can see the results of each test case.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Test files in Deno are just regular TypeScript files, so you can use any TypeScript feature you like to write your tests. This includes using helper functions to generate multiple test cases, as we&#39;ve seen in this article.&lt;/p&gt;
&lt;p&gt;The key takeaway is that you can use &lt;code&gt;Deno.test()&lt;/code&gt; inside a helper function or a for loop to generate multiple test cases. This allows you to run parameterized tests in Deno, and get detailed information about which test cases failed.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>A journey through Server Side Rendering in Deno</title>
    <link href="https://codemade.net/blog/a-journey-through-deno-ssr/" />
    <updated>2025-02-11T00:00:00Z</updated>
    <id>https://codemade.net/blog/a-journey-through-deno-ssr/</id>
    <content type="html">&lt;p&gt;As a longtime .net developer, I&#39;ve always preferred languages with strong typing. I find that I can reason about the code much better if I understand what data structures are required by a function call, and what it will return.&lt;/p&gt;
&lt;p&gt;Naturally, this means that when doing any frontend work, I tend to reach for TypeScript. I find it&#39;s a welcome improvement over vanilla JavaScript, especially when using typed third-party libraries.&lt;/p&gt;
&lt;p&gt;There is only one catch: setting up typescript, esm modules, and bundling can be really painful when you&#39;re just trying to get a simple project off the ground. Soon you&#39;re 2 hours in, and you&#39;re still trying to figure out why your imports aren&#39;t working.&lt;/p&gt;
&lt;p&gt;This is where Deno comes in! Built by the creators of Node.js, deno sets out to undo the mistakes of their previous project. It has built-in support for TypeScript, esm modules, and even has a built-in bundler. This means that I can write all my code in TypeScript, and run it without any additional setup. It also does away with the &lt;code&gt;node_modules&lt;/code&gt; folder, which makes my hard drive very happy.&lt;/p&gt;
&lt;p&gt;So, when starting a new project recently, I decided to give Deno a try. The project is a simple server-side rendered web application, with a bit of interactivity sprinkled in.&lt;/p&gt;
&lt;p&gt;It&#39;s now been a few weeks, and I&#39;ve been through several iterations. Here is &lt;em&gt;my&lt;/em&gt; journey through server-side rendering in Deno.&lt;/p&gt;
&lt;h2&gt;Deno has a built-in web server??&lt;/h2&gt;
&lt;p&gt;Yes, yes it does. Using the builtin &lt;code&gt;Deno.Serve()&lt;/code&gt; function, you can create a simple web server &lt;a href=&quot;https://docs.deno.com/api/deno/~/Deno.serve&quot;&gt;in just a few lines of code&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The only downside of this approach is that you&#39;ll have to handle absolutely everything yourself. This means parsing the request, handling the response, and even serving static files. This is easy enough for a simple project, but it can quickly become unwieldy as your project grows.&lt;/p&gt;
&lt;h2&gt;Shopping for a framework&lt;/h2&gt;
&lt;p&gt;After deciding that I didn&#39;t want to write my own web server, I started looking for a lightweight framework that would handle the heavy lifting for me. I quickly found &lt;a href=&quot;https://oakserver.org/&quot;&gt;Oak&lt;/a&gt;, a simple framework for Deno that is very similar to Express.js.&lt;/p&gt;
&lt;p&gt;Oak has built-in support for things like middleware and routing. It seemed like a great choice for a simple project, and it&#39;s easy to get started with.&lt;/p&gt;
&lt;p&gt;The next step was to figure out a way to go beyond serving API endpoints and start rendering HTML on the server. This is where things started to get interesting.&lt;/p&gt;
&lt;h2&gt;Shopping for a templating engine&lt;/h2&gt;
&lt;h3&gt;Handlebars&lt;/h3&gt;
&lt;p&gt;When thinking of templating engines, my first idea was to use good old &lt;a href=&quot;https://handlebarsjs.com/&quot;&gt;Handlebars&lt;/a&gt;. I had never used it before, but I remember it being all the rage a few years ago, so I figured it must be good. Right? &lt;em&gt;Right??&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;It was... below my expectations. And by &amp;quot;below my expectations&amp;quot; I mean &lt;em&gt;&amp;quot;It makes me want to stab myself&amp;quot;&lt;/em&gt;. The syntax, oh the syntax!! I have nothing but compassion for anyone who has to coexist with it.&lt;/p&gt;
&lt;p&gt;The last straw was when I had to declare a helper function to compare two values. I had to write a helper function to compare two values. In a templating engine.&lt;/p&gt;
&lt;h3&gt;ejs&lt;/h3&gt;
&lt;p&gt;After a good old web search for &amp;quot;handlebars alternatives&amp;quot;, I stumbled upon &lt;a href=&quot;https://ejs.co/&quot;&gt;ejs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As a .net developer, I was immediately reminded of Razor Pages / MVC. Simple, clean, and easy to use. I was sold.&lt;/p&gt;
&lt;p&gt;...it did however come with its own set of quirks. For example, it doesn&#39;t have the equivalent of &amp;quot;Layouts&amp;quot; in Razor. This means that you have to manually include the header and footer in every page. It also has even weirder syntax than Razor, with various combinations of percent signs and angle brackets.&lt;/p&gt;
&lt;p&gt;But mainly, the thing I was really missing until now was being able to &lt;strong&gt;unit test my UI&lt;/strong&gt;. Not knowing if my partials were rendering correctly until I manually tested them in the browser was a pain.&lt;/p&gt;
&lt;p&gt;In the end, I found that while the syntax was cozy and familiar, the web has moved on. I wanted something more modern, more... reactive. I wanted, in fact, React.&lt;/p&gt;
&lt;h3&gt;Detour to the promised land of Deno Fresh&lt;/h3&gt;
&lt;p&gt;Before I could start using React, I had to figure out how to get it to work with Deno. This is where I found &lt;a href=&quot;https://fresh.deno.dev/&quot;&gt;🍋 Deno Fresh&lt;/a&gt;. Fresh is Deno’s answer to Next.js. With built-in support for JSX and an &amp;quot;islands&amp;quot; architecture that prioritizes minimal client-side JavaScript, it looked very promising.&lt;/p&gt;
&lt;p&gt;I quickly cloned the example repo on my machine to try it out. It worked great. Sure, it&#39;s extremely opinionated, but I can live with that.&lt;/p&gt;
&lt;p&gt;I decided to try converting one of my ejs partials into a jsx component right inside the example project. Easy. Done. I was sold. Writing JSX instead of ejs felt natural, the way it was supposed to be all along.&lt;/p&gt;
&lt;p&gt;At some point, however, things started to fall apart.&lt;/p&gt;
&lt;p&gt;Testing components, for example, is not really supported out of the box. There&#39;s &lt;a href=&quot;https://github.com/denoland/fresh/issues/427&quot;&gt;an open issue on github&lt;/a&gt; and the consensus seems to be... let&#39;s wait for Fresh 2.0.&lt;/p&gt;
&lt;p&gt;As far as I understand it, the wind behind Fresh&#39;s sails is now more like a gentle breeze.
It seems that the team has been busy working on Deno itself, and Fresh has been left behind.&lt;/p&gt;
&lt;p&gt;I notice as I write this that the &lt;a href=&quot;https://deno.com/blog/fresh-1.6&quot;&gt;last official blog about it&lt;/a&gt; dates December 1, 2023, which is about 10 years in trendy javascript framework time.&lt;/p&gt;
&lt;h3&gt;But I like react! Maybe I can salvage this?&lt;/h3&gt;
&lt;p&gt;So it turns out that Deno Fresh uses &lt;code&gt;preact-render-to-string&lt;/code&gt; under the hood to transform JSX components into HTML.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;With some clever refactoring, I&#39;m sure I can just replace my ejs Oak middleware with a React one!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;-famous last words&lt;/p&gt;
&lt;p&gt;So I did! I created a new middleware that would render my React components server-side, and it worked like a charm. If you&#39;re curious to see what it looks like, &lt;a href=&quot;https://gist.github.com/LBognanni/4ce0247276b7cd77e72a9cc014cd6e12&quot;&gt;here is a github gist with the code&lt;/a&gt; 🙂&lt;/p&gt;
&lt;p&gt;I now had a working server-side rendered web app in Deno under my belt. I was happy with the result, even if it felt a bit of a Frankenstein&#39;s monster.&lt;/p&gt;
&lt;h2&gt;And then I built a second project&lt;/h2&gt;
&lt;p&gt;Some time later, I wanted to start a new project. Similar requirements, so I figured I could just copy-paste the code from the first project and be done with it.&lt;/p&gt;
&lt;p&gt;But then I thought,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If I need this, maybe others do too? Maybe I should make a library out of it? Maybe I should make my own framework? One framework to rule them all! 😈&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Yeah, no. No thanks Satan. I&#39;m good.&lt;/p&gt;
&lt;p&gt;An important requirement of my second project was user management, authentication, and authorization.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://better-auth.vercel.app/&quot;&gt;Better-Auth&lt;/a&gt; looked exactly like the kind of thing I needed. Fully featured, popular, good documentation. I had absolutely no intention of trying to bend my own little framework to work with it. I just wanted to get my project done.&lt;/p&gt;
&lt;p&gt;I also had the nagging feeling in the back of my head that I was just &lt;em&gt;writing too much code&lt;/em&gt; for something that should be simple.&lt;/p&gt;
&lt;h2&gt;Enter Hono&lt;/h2&gt;
&lt;p&gt;The first backend framework that is mentioned in Better-Auth&#39;s documentation is &lt;a href=&quot;https://hono.dev/&quot;&gt;Hono&lt;/a&gt;. I decided to take a look.&lt;/p&gt;
&lt;p&gt;Hono does everything I was trying to build manually—but better. It has &lt;a href=&quot;https://hono.dev/docs/guides/jsx#jsx&quot;&gt;built-in JSX support&lt;/a&gt;, a router, and great documentation. The best thing is that I don&#39;t have to support any of it myself!&lt;/p&gt;
&lt;p&gt;It also looks almost exactly like Oak, so that was already a plus.&lt;/p&gt;
&lt;p&gt;Unsurprisingly, building my second project was a much smoother experience.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This was a long journey, but a rewarding one. The nice thing was that at every point during my first project I ended up having something that was working, and that I could build upon.&lt;/p&gt;
&lt;p&gt;Refactoring my code to use the next thing was luckily always pretty easy, and I was able to learn a lot along the way.&lt;/p&gt;
&lt;p&gt;One thing I learned is that it&#39;s easy to fall into the trap of doing everything &amp;quot;the Deno way&amp;quot;, and assuming that a library or framework that&#39;s written for Deno is the best choice.&lt;/p&gt;
&lt;p&gt;In reality, it&#39;s important to evaluate each library or framework on its own merits, and choose the one that best fits your needs.&lt;/p&gt;
&lt;p&gt;Frameworks like Hono that are built for multiple runtimes are obviously going to be more popular and better maintained than something that&#39;s built for Deno only.&lt;/p&gt;
&lt;p&gt;The great thing about Deno is that you can reference npm modules directly (without worrying about terabytes of &lt;code&gt;node_modules&lt;/code&gt;), so you really have the whole JavaScript ecosystem at your disposal.&lt;/p&gt;
&lt;h3&gt;🔑🥡 Key Takeaways:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Don’t assume that a library made for Deno is always the best choice—evaluate based on actual needs.&lt;/li&gt;
&lt;li&gt;Multi-runtime frameworks like Hono tend to be better maintained and more flexible.&lt;/li&gt;
&lt;li&gt;Deno’s ability to use npm modules expands its ecosystem significantly.&lt;/li&gt;
&lt;li&gt;Don’t be afraid to refactor and try new things — each iteration can teach you something new!&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Running Docker in Windows without virtualization</title>
    <link href="https://codemade.net/blog/docker-desktop-with-remote-server/" />
    <updated>2025-01-21T00:00:00Z</updated>
    <id>https://codemade.net/blog/docker-desktop-with-remote-server/</id>
    <content type="html">&lt;p&gt;Docker is an essential tool for developers. It allows you to run applications in isolated containers, making it easy to manage dependencies and run the same code in different environments.&lt;/p&gt;
&lt;p&gt;However, on Windows Docker requires virtualization support, which may not always be enabled or available. Even when using WSL2 as an alternative to Hyper-V, your machine must still support and enable Hyper-V.&lt;/p&gt;
&lt;p&gt;Recently I needed to run docker on a Windows virtual machine, running inside VirtualBox on a Linux host.&lt;/p&gt;
&lt;p&gt;Contrary to KVM/QEMU, VirtualBox does not support nested virtualization, &lt;em&gt;unless&lt;/em&gt; you&#39;re using VirtualBox inside the VM too.&lt;/p&gt;
&lt;p&gt;What VirtualBox offers, however, is much better (read: acceptable) video hardware acceleration compared to QEMU - and for someone like me who spends more time attending remote meetings than running containers, this is a good tradeoff.&lt;/p&gt;
&lt;p&gt;I &lt;em&gt;do&lt;/em&gt; however need to run containers from time to time, and having to switch to a different machine just for that is a bit of a hassle.&lt;/p&gt;
&lt;div class=&quot;callout&quot;&gt;
&lt;p&gt;⚠️&lt;/p&gt;
&lt;p&gt;
  &lt;strong&gt;Important!&lt;/strong&gt; We&#39;re going to install Docker Desktop in this guide &amp;mdash; however, without Hyper-V &lt;strong&gt;you won&#39;t be able&lt;/strong&gt; to run the docker engine or &lt;strong&gt;do anything in the GUI&lt;/strong&gt;.
&lt;/p&gt;
&lt;p&gt;
  Instead, we&#39;re relying on Docker desktop to provide the &lt;code&gt;docker&lt;/code&gt; command line tool, which we&#39;ll be using to connect to a Linux server.
&lt;/p&gt;
&lt;/div&gt;
&lt;h2&gt;Step 1: Install Docker Desktop&lt;/h2&gt;
&lt;p&gt;Most online resources will point you to Docker Toolbox, which as of 2025 is a legacy solution that is no longer maintained. While you &lt;em&gt;can&lt;/em&gt; still download it and install it, it&#39;s stuck on a very old version of Docker and is missing some crucial components like &lt;code&gt;buildx&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here is the link to the &lt;a href=&quot;https://docs.docker.com/desktop/setup/install/windows-install/&quot;&gt;Docker Desktop download page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Docker Desktop should install just fine, but of course it will complain if you try to run it.&lt;/p&gt;
&lt;h2&gt;Step 2: Install Docker on the remote server&lt;/h2&gt;
&lt;p&gt;If you haven&#39;t already done so, you&#39;ll need to install Docker on the remote server. There are plenty of resources online on how to do this, so I won&#39;t go into details here. The official docker docs have plenty of information on how to install Docker on different platforms.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.docker.com/engine/install/ubuntu/&quot;&gt;Here&#39;s the link to the instructions for Ubuntu&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Be sure to add your user to the &lt;code&gt;docker&lt;/code&gt; group so you can run docker commands without &lt;code&gt;sudo&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Step 2: Enable ssh on the remote server&lt;/h2&gt;
&lt;p&gt;You&#39;ll need to be able to connect to the remote server via ssh. If you&#39;re using a cloud provider, you&#39;ll likely already have this set up. If you&#39;re using a local server, or Desktop Ubuntu, you&#39;ll need to install an ssh server.&lt;/p&gt;
&lt;p&gt;This is simply done by running:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;apt&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; openssh-server&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You&#39;ll likely also need to open the ssh port on your firewall.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; ufw allow &lt;span class=&quot;token function&quot;&gt;ssh&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Step 3: Configure Docker Desktop&lt;/h2&gt;
&lt;p&gt;Easy peasy. You just need to set an environment variable to tell Docker Desktop to connect to the remote server.&lt;/p&gt;
&lt;p&gt;Try it out in a terminal window. First, set the variable:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$env&lt;/span&gt;:DOCKER_HOST&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ssh://your-username@your-server:22&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then run a docker command:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token function&quot;&gt;docker&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ps&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should get a prompt asking you to accept the server&#39;s fingerprint, then another prompt asking for your password. If everything goes well, you should see the list of containers running on the remote server. (none if you just installed Docker)&lt;/p&gt;
&lt;h2&gt;Step 4: Configure a Persistent Environment Variable&lt;/h2&gt;
&lt;p&gt;You don&#39;t want to have to set the environment variable every time you open a terminal window. Instead, we can set the environment variable for all of Windows.&lt;/p&gt;
&lt;p&gt;Right-click on the start button and select &amp;quot;System&amp;quot;. Then click on &amp;quot;Advanced system settings&amp;quot; and then &amp;quot;Environment Variables&amp;quot;.&lt;/p&gt;
&lt;p&gt;Add a new system variable with the name &lt;code&gt;DOCKER_HOST&lt;/code&gt; and the value &lt;code&gt;ssh://your-username@your-server&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Any new terminal window you open will now be using the environment variable, and docker commands will connected to the remote server.&lt;/p&gt;
&lt;h2&gt;Step 5: Usability improvements&lt;/h2&gt;
&lt;p&gt;As it is, Docker will ask for your password every time you run a command. This can be a very annoying, especially if you&#39;re running a lot of commands. Some commands, like &lt;code&gt;docker buildx build&lt;/code&gt; implicitly run multiple docker commands, so you&#39;ll be asked for your password multiple times.&lt;/p&gt;
&lt;p&gt;Instead, we can use ssh keys to authenticate.&lt;/p&gt;
&lt;p&gt;On your Windows machine, generate a new ssh key pair:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;ssh-keygen&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You&#39;ll be asked where to store the key. Be sure to enter this path: &lt;code&gt;C:&#92;Users&#92;your-username&#92;.ssh&#92;id_rsa&lt;/code&gt;. This is the default path where Docker will look for the key. (Some guides will tell you to just accept the default path, but doing so made the ssh-keygen command hang indefinitely in my case.)&lt;/p&gt;
&lt;p&gt;You&#39;ll be then asked for a passphrase. I recommend leaving this empty so you don&#39;t have to enter it every time you run a docker command.&lt;/p&gt;
&lt;p&gt;The next step is to copy the public key to the remote server. You can do this with the &lt;code&gt;ssh-copy-id&lt;/code&gt; command, but this command is not available on Windows.&lt;/p&gt;
&lt;p&gt;Instead, we can copy the key manually.&lt;/p&gt;
&lt;p&gt;First, print the public key:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token function&quot;&gt;cat&lt;/span&gt; ~&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;.ssh&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;id_rsa.pub&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Copy the whole line that is printed.
Next, connect to the remote server and add the key at the end of the &lt;code&gt;authorized_keys&lt;/code&gt; file, using your favorite text editor (&lt;code&gt;nano&lt;/code&gt; in this case):&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token function&quot;&gt;ssh&lt;/span&gt; your-username@your-server
&lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-p&lt;/span&gt; ~/.ssh
&lt;span class=&quot;token function&quot;&gt;nano&lt;/span&gt; ~/.ssh/authorized_keys&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is it! You should now be able to run docker commands without being asked for your password. Try by running &lt;code&gt;docker ps&lt;/code&gt; again in a new terminal window.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;While this article focussed on connecting docker to a local server, you can also follow the same steps to connect to a server in the cloud.&lt;/p&gt;
&lt;p&gt;This can be useful if you want to run docker commands on a more powerful machine, or if you want to run docker commands on a machine that is serving web traffic to the internet!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Announcing CodeMade Games!</title>
    <link href="https://codemade.net/blog/announcing-codemade-games/" />
    <updated>2025-01-07T00:00:00Z</updated>
    <id>https://codemade.net/blog/announcing-codemade-games/</id>
    <content type="html">&lt;div class=&quot;callout&quot;&gt;
  &lt;p&gt;🕹️&lt;/p&gt;
  &lt;p&gt;
    &lt;b&gt;TLDR&lt;/b&gt; 
    I&#39;m excited to announce the launch of &lt;a href=&quot;https://games.codemade.net&quot;&gt;CodeMade Games&lt;/a&gt;, a new website dedicated to fun, casual games that you can play in your browser 🎉
  &lt;/p&gt;
&lt;/div&gt;
&lt;h3&gt;My game development journey&lt;/h3&gt;
&lt;p&gt;Since I started playing videogames in my childhood, I&#39;ve always been fascinated by the idea of creating my own games. Building games takes a lot of time and effort, but it&#39;s also incredibly rewarding.&lt;/p&gt;
&lt;p&gt;I had somewhat of a failed start many, many years ago when I tried to build &lt;a href=&quot;https://www.gljakal.com/blog/2010/08/16/screenshot-update/&quot;&gt;a platformer game&lt;/a&gt; in C++. While I did get some traction initially, with a nice prototype and some basic game mechanics, I eventually lost interest and moved on to other projects. One of the main reasons for this was that I have no artistic skills whatsoever, and the little art I could produce would take many long hours to create.&lt;/p&gt;
&lt;h3&gt;A new beginning&lt;/h3&gt;
&lt;p&gt;Fast forward to a few weeks ago, when I wanted a break from my usual backend development work and decided to play around with the &lt;code&gt;:has&lt;/code&gt; CSS selector.
I ended up building &lt;a href=&quot;https://games.codemade.net/swep/index.html&quot;&gt;a simple minesweeper clone&lt;/a&gt; - and had a lot of fun doing it!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://games.codemade.net/sweep.png&quot; alt=&quot;A screenshot of Sweep, my minesweeper clone&quot;&gt;&lt;/p&gt;
&lt;p&gt;After comprehensively testing the game on my couch, on my way to work, and during my lunch breaks, I decided that it was time to release it to the world.
And so, &lt;a href=&quot;https://games.codemade.net&quot;&gt;CodeMade Games&lt;/a&gt; was born!&lt;/p&gt;
&lt;h3&gt;My core game development principles&lt;/h3&gt;
&lt;p&gt;It&#39;s been almost 15 years since I last worked on a game, and I&#39;ve learned a lot since then. Here are some of the core principles that I&#39;m following with CodeMade Games:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Do less&lt;/strong&gt;: Like, way less. No fancy graphics, no complex mechanics. Art was always my biggest blocker, so I&#39;m focusing on simple, available assets. Emojis are a great way to add some personality to the games without spending hours on art! 🔥&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Build for the web&lt;/strong&gt;: I want my games to be accessible to everyone, so I&#39;m building them to be played in the browser. Mobile is also a priority, mostly because I want to be able to play them on my phone!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Respect the user&lt;/strong&gt;: Games should be fun, not annoying. With most game companies trying to &lt;a href=&quot;https://www.howtogeek.com/fortnite-was-busted-for-using-dark-patterns-heres-what-that-means/&quot;&gt;manipulate their users&lt;/a&gt; into spending more time and money, I want to take a different approach. No ads, no invasive tracking, no dark patterns. Have your 10 minutes of fun and then go back to your life!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Learn something&lt;/strong&gt;: Writing games is my way to relax and have fun, but I also want to learn new things. Whether it&#39;s a new CSS trick, a new JavaScript feature, or a new game design pattern, I want to keep pushing myself to learn and improve.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;What&#39;s next?&lt;/h3&gt;
&lt;p&gt;I&#39;m working on a few new games that I&#39;m excited to share with you soon.&lt;/p&gt;
&lt;p&gt;I&#39;m also planning to write more about my game development journey, the tools I&#39;m using, and the lessons I&#39;m learning along the way.&lt;/p&gt;
&lt;p&gt;Be sure to subscribe to the &lt;a href=&quot;https://codemade.net/atom.xml&quot;&gt;RSS feed&lt;/a&gt; or follow me on &lt;a href=&quot;https://bsky.app/profile/loris.codes&quot;&gt;BlueSky&lt;/a&gt; to stay updated!&lt;/p&gt;
&lt;h4&gt;And of course, see you on &lt;a href=&quot;https://games.codemade.net&quot;&gt;CodeMade Games&lt;/a&gt; 🎮&lt;/h4&gt;
</content>
  </entry>
  <entry>
    <title>Modern CSS is awesome!</title>
    <link href="https://codemade.net/blog/modern-css-is-awesome/" />
    <updated>2024-11-25T00:00:00Z</updated>
    <id>https://codemade.net/blog/modern-css-is-awesome/</id>
    <content type="html">&lt;p&gt;The last time I seriously worked with CSS was back when Firefox was still a relevant browser (😢), and Internet Explorer 11 was &lt;em&gt;just&lt;/em&gt; on its way out.&lt;/p&gt;
&lt;p&gt;Flexbox had just become a popular way to create layouts, and CSS Grid was new and exciting, but not yet widely supported.
Not that I could use either, since IE&#39;s implementation of flexbox was so riddled with bugs that it was practically unusable.&lt;/p&gt;
&lt;p&gt;But now. We&#39;re in 2024, and things have changed. A lot.&lt;/p&gt;
&lt;p&gt;Here are a few of my favourite features of modern CSS, in no particular order:&lt;/p&gt;
&lt;h3&gt;CSS Variables&lt;/h3&gt;
&lt;p&gt;CSS variables were once only possible with a preprocessor like SASS or LESS, but now they&#39;re a native feature of CSS.&lt;/p&gt;
&lt;p&gt;Here is how to define a variable:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;:root&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;--primary-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #ff0000&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Specifying the variable at the root level makes it available to the entire document.&lt;/p&gt;
&lt;p&gt;The variable can then be used with the &lt;code&gt;var()&lt;/code&gt; function:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--primary-color&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The neat thing is that variables follow the cascading logic of CSS, so you can update their value at any level of the document, and all elements that use that variable will automatically update.&lt;/p&gt;
&lt;p class=&quot;codepen&quot; data-height=&quot;300&quot; data-default-tab=&quot;css,result&quot; data-slug-hash=&quot;XJrJvom&quot; data-pen-title=&quot;CSS Variables&quot; data-user=&quot;codemade&quot; style=&quot;height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot;&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/codemade/pen/XJrJvom&quot;&gt;
  CSS Variables&lt;/a&gt; by Loris Bognanni (&lt;a href=&quot;https://codepen.io/codemade&quot;&gt;@codemade&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;h3&gt;Nesting in CSS&lt;/h3&gt;
&lt;p&gt;This is again one of those things that you would have needed a CSS preprocessor for in the past, but is now a native feature of CSS. Useful for keeping your code concise and readable.&lt;/p&gt;
&lt;pre class=&quot;language-scss&quot;&gt;&lt;code class=&quot;language-scss&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.parent &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; red&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token selector&quot;&gt;.child &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; blue&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I especially find this useful when styling pseudo-elements and states:&lt;/p&gt;
&lt;pre class=&quot;language-scss&quot;&gt;&lt;code class=&quot;language-scss&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;button &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; blue&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token selector&quot;&gt;&lt;span class=&quot;token parent important&quot;&gt;&amp;amp;&lt;/span&gt;:hover &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; red&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token selector&quot;&gt;&lt;span class=&quot;token parent important&quot;&gt;&amp;amp;&lt;/span&gt;:after &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;🚀&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Centering a &lt;code&gt;div&lt;/code&gt; is now very easy&lt;/h3&gt;
&lt;p&gt;Centerig a &lt;code&gt;div&lt;/code&gt; has long been a recurring joke amongst web developers, but thanks to CSS Grid, it&#39;s now a two-liner:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;div.parent&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; grid&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;place-items&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; center&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Viewport units&lt;/h3&gt;
&lt;p&gt;Viewport units are a way to size elements based on the size of the viewport. They are particularly useful for creating responsive designs.&lt;/p&gt;
&lt;p&gt;Viewport units include &lt;code&gt;vw&lt;/code&gt; (viewport width), &lt;code&gt;vh&lt;/code&gt; (viewport height), &lt;code&gt;vmin&lt;/code&gt; (the smaller of the two), and &lt;code&gt;vmax&lt;/code&gt; (the larger of the two). So for example, if you want an element to be half as wide as the viewport, you can do this:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.my-element&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 50vw&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is an example that combines viewport units and CSS Grid to center a div in the middle of the screen:&lt;/p&gt;
&lt;p class=&quot;codepen&quot; data-height=&quot;300&quot; data-default-tab=&quot;css,result&quot; data-slug-hash=&quot;JoPogQW&quot; data-pen-title=&quot;centering&quot; data-user=&quot;codemade&quot; style=&quot;height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot;&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/codemade/pen/JoPogQW&quot;&gt;
  centering&lt;/a&gt; by Loris Bognanni (&lt;a href=&quot;https://codepen.io/codemade&quot;&gt;@codemade&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;h3&gt;Math in CSS&lt;/h3&gt;
&lt;p&gt;CSS has a &lt;code&gt;calc()&lt;/code&gt; function that allows you to perform calculations right in your stylesheets. This can be useful for things like calculating widths, margins, and padding.&lt;/p&gt;
&lt;p&gt;The exciting thing about &lt;code&gt;calc()&lt;/code&gt; is that it can combine different units of measurement, so you can do things like this:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.my-element&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;50% - 20px&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can of course use &lt;code&gt;calc()&lt;/code&gt; with variables as well:&lt;/p&gt;
&lt;pre class=&quot;language-scss&quot;&gt;&lt;code class=&quot;language-scss&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;:root &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;--margin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 20px&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token selector&quot;&gt;article &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;margin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;calc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;--margin&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; 2&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;CSS Containers, container queries, and container measurements&lt;/h3&gt;
&lt;p&gt;By telling CSS that a certain element is a &lt;code&gt;container&lt;/code&gt;, you can now do things like size its children based on the container&#39;s size, or apply styles based on the container&#39;s size.&lt;/p&gt;
&lt;p&gt;Similar to viewport units, container units are based on the size of the container element. They include &lt;code&gt;cqw&lt;/code&gt;, &lt;code&gt;cqh&lt;/code&gt;, &lt;code&gt;cqmin&lt;/code&gt;, and &lt;code&gt;cqmax&lt;/code&gt;.&lt;/p&gt;
&lt;p class=&quot;codepen&quot; data-height=&quot;300&quot; data-default-tab=&quot;css,result&quot; data-slug-hash=&quot;wBwawvN&quot; data-pen-title=&quot;Untitled&quot; data-user=&quot;codemade&quot; style=&quot;height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot;&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/codemade/pen/wBwawvN&quot;&gt;
  Untitled&lt;/a&gt; by Loris Bognanni (&lt;a href=&quot;https://codepen.io/codemade&quot;&gt;@codemade&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_containment/Container_queries&quot;&gt;Container queries&lt;/a&gt; take this a step further by allowing you to apply styles based on the size of the container. This is particularly useful for creating responsive designs where the layout changes based on the size of the container, rather than the viewport.&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;aspect-ratio&lt;/code&gt; property&lt;/h3&gt;
&lt;p&gt;If you ever wanted to embed a video in your responsive page in the old days, you probably had to use some javascript to calculate the correct height based on the width of the video.&lt;/p&gt;
&lt;p&gt;But now, you can use the &lt;code&gt;aspect-ratio&lt;/code&gt; property:&lt;/p&gt;
&lt;pre class=&quot;language-scss&quot;&gt;&lt;code class=&quot;language-scss&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.video &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 100%&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;aspect-ratio&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 16 &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; 9&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;code&gt;:has()&lt;/code&gt; selector&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;:has()&lt;/code&gt; selector allows you to select an element based on its descendants. This can be useful for styling elements based on their content, or for selecting elements that contain a specific child element.&lt;/p&gt;
&lt;p&gt;While before &lt;code&gt;:has&lt;/code&gt; you would have to use javascript or some sort of preprocessing to achieve the same effect, now you can do it with a single line of CSS.&lt;/p&gt;
&lt;p&gt;For example, here is a simple way to style a &lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; element when its child &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; is checked:&lt;/p&gt;
&lt;p class=&quot;codepen&quot; data-height=&quot;200&quot; data-default-tab=&quot;css,result&quot; data-slug-hash=&quot;gbYpbgG&quot; data-pen-title=&quot;:has demo&quot; data-user=&quot;codemade&quot; style=&quot;height: 200px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot;&gt;
  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/codemade/pen/gbYpbgG&quot;&gt;
  :has demo&lt;/a&gt; by Loris Bognanni (&lt;a href=&quot;https://codepen.io/codemade&quot;&gt;@codemade&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
&lt;/p&gt;
&lt;hr&gt;
&lt;script async=&quot;&quot; src=&quot;https://cpwebassets.codepen.io/assets/embed/ei.js&quot;&gt;&lt;/script&gt;</content>
  </entry>
  <entry>
    <title>The case of the unhappy CloudFront distribution</title>
    <link href="https://codemade.net/blog/the-case-of-the-unhappy-cloudfront/" />
    <updated>2024-06-29T00:00:00Z</updated>
    <id>https://codemade.net/blog/the-case-of-the-unhappy-cloudfront/</id>
    <content type="html">&lt;p&gt;For fun, and as a learning experience, I&#39;m building a very simple comment system for this blog.&lt;/p&gt;
&lt;p&gt;Currently codemade.net is a simple static website built by Jekyll everytime I push to the &lt;code&gt;main&lt;/code&gt; branch on its &lt;a href=&quot;https://github.com/LBognanni/codemade-site&quot;&gt;GitHub repo&lt;/a&gt;.
This is a relatively common setup for smaller websites, and it has the benefit of being extremely fast and cheap to host.&lt;/p&gt;
&lt;p&gt;There are a number of third party comment systems floating around the internet, but they all have some drawbacks. Some are just too expensive for a small website like mine, others are advertising platforms in disguise, and others still are just too complicated to set up. Perhaps this is why a lot of people rely on social media for comments.&lt;/p&gt;
&lt;p&gt;I wanted to build something that was simple, cheap and cheeful. I also wanted to get hands on with tools I rarely use, like ASP.net Core on Lambda, HTMX, terraform, CloudFront, etc.&lt;/p&gt;
&lt;p&gt;It follows a relatively common pattern: all the static content is stored in S3, while the dynamic APIs are served by Lambda functions. An HTTP API gateway is used to route requests to the correct Lambda function, and CloudFront sits in front of everything to provide caching, routing, and SSL.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://codemade.net/images/basic-website.png&quot; alt=&quot;The architecture of the comment system&quot;&gt;&lt;/p&gt;
&lt;p&gt;I built and deployed this in pieces, starting with the main Lambda function. I set it up to serve both the frontend (a simple HTML page), and the backend: a GET endpoint to return the comments for a post, a PUT endpoint to post a new comment. I tested it locally, and it worked fine.&lt;/p&gt;
&lt;p&gt;I deployed it to AWS, behind an API Gateway, and surprisingly, it worked!&lt;/p&gt;
&lt;p&gt;I then decided to add CloudFront in front of the API Gateway, and store the static content in an S3 bucket.&lt;/p&gt;
&lt;p&gt;When configuring CloudFront, one must set up one or more &amp;quot;Origins&amp;quot; (the places where CloudFront will fetch the content from). I set up two origins: the default one being S3 where &lt;code&gt;index.html&lt;/code&gt; is stored, and the API Gateway, only for the &lt;code&gt;/comments/*&lt;/code&gt; path.&lt;/p&gt;
&lt;p&gt;Deploy the distribution, and yes! The index page loads, and navigating to a comments page brings up a list of comments.&lt;/p&gt;
&lt;p&gt;Try posting a comment however, and... this happens:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://codemade.net/images/cloudfront-is-not-happy.png&quot; alt=&quot;CloudFront returns HTTP 403&quot;&gt;&lt;/p&gt;
&lt;p&gt;Soo, somehow GETting the comments works, but PUTing a comment doesn&#39;t.&lt;/p&gt;
&lt;p&gt;I started debugging by looking at each piece of the puzzle:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Does the lambda work?&lt;/strong&gt; Yes, testing the lambda in isolation yields the expected result (a comment is posted)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Does the API Gateway work?&lt;/strong&gt; Yes, invoking the API gateway URL directly calls the lambda and posts a comment&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Did I set up the CloudFront distribution correctly?&lt;/strong&gt; I think so, but let&#39;s check the settings. I double-checked the origins, and they look fine. I also checked the behaviors, and indeed requests to &lt;code&gt;/comments/*&lt;/code&gt; are routed to the API Gateway &lt;em&gt;(of course they are! GET requests work!)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Maybe it&#39;s a caching issue? I tried invalidating the cache, but it didn&#39;t help.&lt;/li&gt;
&lt;li&gt;Maybe it&#39;s a CORS issue? I tested by directly invoking the PUT endpoint via Postman, and it worked. So probably it&#39;s not that.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Hmm. Perhaps the CloudFront logs could help me? I enabled logging, and tried to post a comment. I then waited for a few minutes, and checked the logs:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://codemade.net/images/cf-logs-1.png&quot; alt=&quot;CloudFront logs&quot;&gt;&lt;/p&gt;
&lt;p&gt;(shoutout to the &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=mechatroner.rainbow-csv&quot;&gt;Rainbow CSV&lt;/a&gt; Vs Code extension for making this somewhat readable!)&lt;/p&gt;
&lt;p&gt;It&#39;s a bit hard to follow, but we can see 3 requests: the first two are GET requests to the &lt;code&gt;favicon.ico&lt;/code&gt; resource (presumably served from S3), and to the a comments page (served by the API Gateway). The third request is a PUT request to the same comments page, and it returns a 403 status code.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;x-edge-result-type&lt;/code&gt; field simply says &lt;code&gt;Miss&lt;/code&gt; for the GET requests (ie they were not in CloudFront&#39;s cache) and &lt;code&gt;Error&lt;/code&gt; for the PUT request. Not very helpful.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;x-edge-detailed-result-type&lt;/code&gt; field looks interesting. It says &lt;code&gt;InvalidRequestMethod&lt;/code&gt; for the PUT request.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Could it be that I didn&#39;t allow PUT requests on the Api Gateway origin?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I checked my terraform code, and nope, all HTTP methods are allowed for that origin 🤔&lt;/p&gt;
&lt;p&gt;So I started googling (well &lt;em&gt;duckduckgoing&lt;/em&gt;) for every combination of &amp;quot;CloudFront&amp;quot;, &amp;quot;InvalidRequestMethod&amp;quot;, &amp;quot;403&amp;quot;, &amp;quot;PUT&amp;quot;, &amp;quot;API Gateway&amp;quot; I could think of.&lt;/p&gt;
&lt;p&gt;I found some StackOverflow posts where folks were having similar issues, but they were all related to not having configured one of the three components (CloudFront, API Gateway, S3) correctly. I double, triple, quadruple checked my configuration, and it all looked fine.&lt;/p&gt;
&lt;p&gt;Out of desperation, I tried mixing things up a bit. I changed the CloudFront distribution to have only one origin: the API Gateway. I then tried posting a comment, and... it worked! But why?&lt;/p&gt;
&lt;p&gt;I was really tempted to just leave it at that, but the nagging voice at the back of my head, complaining that I would have to invoke the lambda for each. single. static. asset. was too loud.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Obviously&lt;/em&gt; the moral imperative is to have the static content served from S3 (it&#39;s cheaper, faster, and more reliable). So I re-enabled the S3 origin, and tried posting a comment again. And... back to not working, of course.&lt;/p&gt;
&lt;p&gt;Okay. Time to take a step back. Go for a walk. Clear my head.&lt;/p&gt;
&lt;p&gt;It&#39;s the next day, and I&#39;m back at it. Time to try and simplify things a bit.&lt;/p&gt;
&lt;p&gt;Perhaps my terraform code was too complex, or it was missing something? I decided to try and set up the CloudFront distribution manually, using the AWS console. Manually add both origin, set up the behaviors, and deploy.&lt;/p&gt;
&lt;p&gt;Nope, same issue.&lt;/p&gt;
&lt;p&gt;There must be a reason.&lt;/p&gt;
&lt;p&gt;Maybe I can look at the CloudFront logs again? There are so many fields. Maybe I missed something? Maybe if I cross my eyes just right it will dawn on me?&lt;/p&gt;
&lt;p&gt;And oh yes, dawn on me it did. The stupid, obvious little thing that I missed. Let me highlight it for you:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://codemade.net/images/cf-logs-2.png&quot; alt=&quot;CloudFront Logs showing a capitalization error on my PUT requests&quot;&gt;&lt;/p&gt;
&lt;p&gt;Do you see it? The GET request is being sent to &lt;code&gt;/comments/foo/bar&lt;/code&gt;, but the PUT request is being sent to &lt;code&gt;/Comments/foo/bar&lt;/code&gt;. Capital C. FML 😩&lt;/p&gt;
&lt;p&gt;So CloudFront was directing our PUT request to the S3 origin, which of course didn&#39;t have the PUT method enabled, and returned a 403.&lt;/p&gt;
&lt;p&gt;Fun fact, because my API gateway was set to proxy all traffic to my lambda, it didn&#39;t care about the capitalization. But CloudFront does.&lt;/p&gt;
&lt;p&gt;AWS explicitly call this out in their &lt;a href=&quot;https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/distribution-web-values-specify.html#DownloadDistValuesPathPattern&quot;&gt;documentation&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://codemade.net/images/cf-path-patterns.png&quot; alt=&quot;A screenshot of the link above&quot;&gt;&lt;/p&gt;
&lt;p&gt;And the best part? Not only I &lt;em&gt;knew&lt;/em&gt; about this, I must have read that page at least 15 times while debugging this.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Anyway, to make a long story short, I fixed my frontend code to PUT to the right path, everything started working as expected 🎉&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I hope you enjoyed this little story. I certainly learned a lot about CloudFront, API Gateway, and Lambda while debugging this. My desk has a fresh head-shaped indentation, but &lt;em&gt;c&#39;est la vie&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Oh, and the comment system is not ready yet 😅, so feel free to discuss this on HN, or Reddit 😅&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>The case of the mysteriously disappearing window</title>
    <link href="https://codemade.net/blog/the-case-of-the-disappearing-window/" />
    <updated>2023-12-16T00:00:00Z</updated>
    <id>https://codemade.net/blog/the-case-of-the-disappearing-window/</id>
    <content type="html">&lt;p&gt;One small annoyance I contend with everyday is links opening in the &amp;quot;wrong&amp;quot; browser. See, I use different browsers for different purposes.&lt;/p&gt;
&lt;p&gt;Chrome is the browser I use for all my work stuff, partially because I prefer its Dev tools and also because most SaaS apps I use for work tend to work better with it. Firefox however is my preferred browser and has the advantage of not being built by an advertisement company, so I tend to do all my personal browsing there. I also use Edge for Netflix because why not.&lt;/p&gt;
&lt;p&gt;So I spent a few hours hacking together &lt;a href=&quot;https://codemade.net/roundabout&quot;&gt;Roundabout&lt;/a&gt;, a simple app that registers itself as a web browser and allows you to pick the browser you&#39;d like to open your link in.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://codemade.net/images/hero-roundabout.png&quot; alt=&quot;Roundabout screenshot&quot;&gt;&lt;/p&gt;
&lt;p&gt;Upon testing, everything looked good: I could click a link in a non-browser application like Telegram Desktop or WhatsApp desktop, and Roundabout would pop up asking me if I&#39;d prefer Chrome or Firefox.&lt;/p&gt;
&lt;h3&gt;...except for Slack&lt;/h3&gt;
&lt;p&gt;As it turned out, clicking a link from Slack would... do nothing?&lt;/p&gt;
&lt;p&gt;I started with the usual debugging techniques: add some logs, test again. Build fails. Wait, why? A quick glance at &lt;a href=&quot;https://learn.microsoft.com/en-us/sysinternals/downloads/process-explorer&quot;&gt;Process Explorer&lt;/a&gt; revealed the issue: Roundabout was starting, but somehow it got stuck and wasn&#39;t showing the main window. After terminating all the stuck instances, I was able to test again, this time with logs.&lt;/p&gt;
&lt;p&gt;The logs looked OK, but just stopped after showing the main form 🤔&lt;/p&gt;
&lt;p&gt;Next it was time to investigate &lt;em&gt;how&lt;/em&gt; exactly Slack starts a web browser.
A few minutes of sleuthing with &lt;a href=&quot;https://learn.microsoft.com/en-us/sysinternals/downloads/procmon&quot;&gt;Process Monitor&lt;/a&gt; got me my answer: when opening links Slack runs a new command, in the form of &lt;code&gt;RunDll32.exe URL.dll,FileProtocolHandler {the URL}&lt;/code&gt;; interesting 🤔.&lt;/p&gt;
&lt;p&gt;If you never crossed paths with it, RunDll32 is perhaps one of the most interesting applications in Windows. It allows one to invoke functions that are defined in Dll files, simply by calling &lt;code&gt;rundll32 {the dll},{the function} {any parameters}&lt;/code&gt;. In this case, we&#39;re executing the &lt;code&gt;FileProtocolHandler&lt;/code&gt; function that is defined in &lt;code&gt;URL.dll&lt;/code&gt;, and passing our URL to it.&lt;/p&gt;
&lt;p&gt;So what happens if I just run that command from a command prompt?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://codemade.net/images/rundll32.png&quot; alt=&quot;A command prompt showing that we executed Rundll32, with a Roundabout window open in front of it&quot;&gt;&lt;br&gt;Hmmm, it works as expected...&lt;/p&gt;
&lt;p&gt;I began to suspect that there was some sort of problem actually showing the form, when starting from Slack. What would happen if I added a MessageBox just before showing the form? Interestingly, the message box showed just fine, and then the main form also showed up fine 🤔&lt;/p&gt;
&lt;p&gt;Perhaps the issue is just that Roundabout shows up too quickly? I added a 1 second pause just before starting and... nothing, the main window wouldn&#39;t show again. I&#39;m kinda relieved because I really didn&#39;t want to add a huge pause before something happens for the user 😌&lt;/p&gt;
&lt;p&gt;Okay, so it looks like Slack &amp;quot;swallows&amp;quot; the first proper window we create. I wonder what happens if we temporarily create an empty window and immediately close it before showing the main window?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://codemade.net/images/roundabout_slack_works.png&quot; alt=&quot;A Roundabout window, open in front of a Slack conversation&quot;&gt;&lt;br&gt;🎉It works!&lt;/p&gt;
&lt;p&gt;At this point, it&#39;s been a few hours debugging this on and off, and a hacky workaround sounds like the perfect solution...&lt;/p&gt;
&lt;p&gt;🧹 Time to clean up the code a bit, and hide this monstrosity behind a helper function.&lt;/p&gt;
&lt;p&gt;It would be also good to only do this when starting from Slack, otherwise users would see an empty window flash on screen. Unfortunately there&#39;s no good way to find out what is your &amp;quot;parent&amp;quot; program, but the user likely clicked on the link with their mouse, so finding the right window based on the pointer coordinates should suffice.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/LBognanni/Roundabout/blob/1b8c675c81628b163bb860fc651c19fb9289c04d/src/Roundabout/Program.cs#L68-L88&quot;&gt;Here is the admittedly horrible code that does this&lt;/a&gt;!&lt;/p&gt;
&lt;h3&gt;Fin.&lt;/h3&gt;
&lt;p&gt;In unraveling the mystery of Slack links not cooperating with Roundabout, we&#39;ve delved into unexpected corners of Windows application development. With the code tweaks and insights gained, Roundabout now seamlessly handles links from various applications, including Slack.&lt;/p&gt;
&lt;p&gt;As with any solution, there&#39;s always room for improvement. Considerations for future versions include refining the code further, and perhaps adpoting this workaround for other apps I missed this time.&lt;/p&gt;
&lt;p&gt;If you do find one such case of an app &amp;quot;swallowing&amp;quot; Roundabout, please do &lt;a href=&quot;https://github.com/LBognanni/Roundabout/issues&quot;&gt;open an issue on Github&lt;/a&gt;!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>How to draw a conic gradient with System.Drawing in gdiPlus</title>
    <link href="https://codemade.net/blog/conic-gradient/" />
    <updated>2021-05-24T00:00:00Z</updated>
    <id>https://codemade.net/blog/conic-gradient/</id>
    <content type="html">&lt;p&gt;Recently, I have been working on adding support for conic gradient (or conical gradients, as some say) to &lt;a href=&quot;https://codemade.net/clock&quot;&gt;Clock&lt;/a&gt;. Now, Clock uses GdiPlus, or the &lt;code&gt;System.Drawing&lt;/code&gt; namespace in C# to do all of its drawing. My aim was to replicate the &lt;code&gt;conic-gradient()&lt;/code&gt; &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/conic-gradient()&quot;&gt;CSS function&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Imagine my surprise when searching online for &amp;quot;gdiplus conic gradient&amp;quot; yielded few and very old results. Ok, maybe you&#39;re not very surprised because all the cool kids are now writing Electron apps and mid-2000s &lt;code&gt;System.Drawing&lt;/code&gt; has been relegated to history 😄&lt;/p&gt;
&lt;p&gt;Like any other methods of filling a shape, we&#39;re going to need a type of &lt;code&gt;Brush&lt;/code&gt; to do it. The one we&#39;re going to use for conic gradients is the &lt;code&gt;PathGradientBrush&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;What&#39;s interesting about &lt;code&gt;PathGradientBrush&lt;/code&gt; is that it can be used in many ways. &lt;a href=&quot;https://www.c-sharpcorner.com/uploadfile/puranindia/783/&quot;&gt;This article from C# Corner&lt;/a&gt; goes over a few, but crucially the one that&#39;s missing is a proper conic gradient.
After a lot of searching, I finally stumbled on &lt;a href=&quot;https://web.archive.org/web/20140906083853/http://bobpowell.net/pgb.aspx&quot;&gt;this article&lt;/a&gt;(the original website is now offline!) and noticed this image and code snippet:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://web.archive.org/web/20140906083853im_/http://bobpowell.net/images/pgb.ht2.jpg&quot; alt=&quot;&amp;quot;Many vertices, few colors&amp;quot;&quot;&gt;&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;  &lt;span class=&quot;token class-name&quot;&gt;GraphicsPath&lt;/span&gt; pth&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;GraphicsPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  pth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;AddEllipse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ClientRectangle&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  
  &lt;span class=&quot;token class-name&quot;&gt;PathGradientBrush&lt;/span&gt; pgb&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;PathGradientBrush&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pth&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  pgb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SurroundColors&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;Color&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                    Color&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Red&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                    Color&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Orange&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                    Color&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Yellow&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                    Color&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Green&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                    Color&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Blue&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                    Color&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Indigo&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                    Color&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Violet &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  pgb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CenterColor&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;Color&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Gray&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Graphics&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;FillRectangle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pgb&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ClientRectangle&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&#39;s not a &amp;quot;real&amp;quot; conic gradient, but close. What&#39;s interesting in that example is that it&#39;s setting a &lt;code&gt;SurroundColors&lt;/code&gt; property, but, because a circle in gdi+ has many vertices, only a tiny portion of the whole is occupied by a gradient. Of course the next step to try was using a polygon with the same number of vertices as the number of colors and check the result:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; pth &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;GraphicsPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; colors &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;Color&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  Color&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Red&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  Color&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Orange&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  Color&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Yellow&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  Color&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Green&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  Color&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Blue&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  Color&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Indigo&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  Color&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Violet &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; rect &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;Rectangle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sz&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sz&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; polyPoints &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;List&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Point&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;double&lt;/span&gt;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&lt;/span&gt; colors&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  polyPoints&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;Point&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sz &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Cos&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;PI &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;double&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;pts&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;double&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sz&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sz &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Sin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;PI &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;double&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;pts&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;double&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sz&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
pth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;AddPolygon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;polyPoints&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ToArray&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token class-name&quot;&gt;PathGradientBrush&lt;/span&gt; pgb &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;PathGradientBrush&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pth&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

pgb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SurroundColors &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; colors&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

g&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;FillRectangle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pgb&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; rect&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://codemade.net/images/poly-gradient-white.png&quot; alt=&quot;close but no dice&quot;&gt;&lt;/p&gt;
&lt;p&gt;It&#39;s close, but where does the white come from? Well if you look closely at the circle example from the article above, there&#39;s a &lt;code&gt;CenterColor&lt;/code&gt; property that is set to Gray there. When you don&#39;t specify it, instead of there being no center color, a white shade is used instead. Setting CenterColor to transparent renders the filling itself transparent so that&#39;s no good either.&lt;/p&gt;
&lt;p&gt;But! &lt;code&gt;PathGradientBrush&lt;/code&gt; has a &lt;code&gt;Blend&lt;/code&gt; property that allows us to control the &lt;code&gt;CenterColor&lt;/code&gt;&#39;s opacity from the center to the border of the shape. We can just set it to fully opaque at both ends to get our gradient:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;pgb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Blend &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;Blend&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  Factors &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  Positions &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1f&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://codemade.net/images/poly-gradient-ok.png&quot; alt=&quot;success!&quot;&gt;&lt;/p&gt;
&lt;p&gt;Isn&#39;t that something! Now to make it really look like the kind of conic gradient we get in css, we have to make it so the last and the first color don&#39;t blend into each other. We can do this by creating a polygon that has one less vertex, where the last Point is repeated. We also need to specify the &lt;code&gt;CenterPoint&lt;/code&gt; property, otherwise Gdi+ will auto-calculate it as being slightly off-center:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://codemade.net/images/poly-gradient-conic.png&quot; alt=&quot;fully conic&quot;&gt;&lt;/p&gt;
&lt;p&gt;One last thing! You might have noticed that, while we&#39;re using &lt;code&gt;FillRectangle&lt;/code&gt; to paint our gradient, we&#39;re not really filling &lt;em&gt;all of it&lt;/em&gt;, but we&#39;re drawing a polygon. This is easily fixed by using a suitably large radius for our polygon.&lt;/p&gt;
&lt;p&gt;If you&#39;re interested to see what I ended up with, the &lt;a href=&quot;https://github.com/LBognanni/CodeMadeClock/blob/master/src/CodeMade.ScriptedGraphics/ConicGradient.cs&quot;&gt;full source code&lt;/a&gt; is open source as part of Clock 😊&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Fix Windows restarting after sleep or hybernate: the complete 2020 guide</title>
    <link href="https://codemade.net/blog/windows-restarts-itself-guide/" />
    <updated>2020-10-25T00:00:00Z</updated>
    <id>https://codemade.net/blog/windows-restarts-itself-guide/</id>
    <content type="html">&lt;p&gt;So your computer starts on its own after you put it to sleep or even after you turn it off. Maybe it does so immediately or maybe it waits for a few minutes or hours. But you feel like you can&#39;t trust it anymore: there&#39;s few things more unsettling than being woken up by the bright light of your screen in the middle of the night. Well here is my comprehensive list of things to check (and to turn off!) to prevent this from happening again.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;First off, open an admin Powershell terminal.&lt;/strong&gt; You can do so by right-clicking the start menu button and selecting &lt;code&gt;Windows PowerShell (Admin)&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;1: Check why the system restarted&lt;/h2&gt;
&lt;p&gt;The first thing to do when diagnosing the problem is to use the &lt;code&gt;powercfg /lastwake&lt;/code&gt; command to find out what prompted the system to restart.
If a hardware device was responsible for restarting windows, you should see it here.&lt;/p&gt;
&lt;p&gt;Sometimes, however, things are more nebulous and you get something like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;❯ powercfg /lastwake
Wake History Count - 1
Wake History [0]
  Wake Source Count - 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this case, follow the next steps to make double sure that nothing can wake up your pc.&lt;/p&gt;
&lt;h2&gt;2: Disable wake timers&lt;/h2&gt;
&lt;p&gt;This is fairly straightforward and should really be all you need.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Open the Start menu and type &lt;code&gt;Edit power plan&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Choose &lt;code&gt;Change advanced power settings&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Navigate to &lt;code&gt;Sleep&lt;/code&gt; -&amp;gt; &lt;code&gt;Allow wake timers&lt;/code&gt; and select &lt;code&gt;Disable&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Press OK and close&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://codemade.net/images/poweroptions.png&quot; alt=&quot;Power options dialog, disable windows wake timers&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;In theory&lt;/em&gt;, the above steps should be enough to stop any device from waking up your computer. In practice, you&#39;ll probably have to explicitly disable more stuff:&lt;/p&gt;
&lt;h2&gt;3: Disable any peripherals that can wake up your computer&lt;/h2&gt;
&lt;p&gt;The main peripherals to take into account here are keyboards, mice and network adapters. The network adapter can be set to wake up your computer whenever it receives any data (a very bad idea!) or when it receives a special &amp;quot;wake up&amp;quot; packet.&lt;/p&gt;
&lt;p&gt;You can check which devices can wake up your pc with the command: &lt;code&gt;powercfg /devicequery wake_armed&lt;/code&gt;. You&#39;ll see something like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;❯ powercfg /devicequery wake_armed
HID-compliant mouse (006)
HID Keyboard Device (006)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now comes the fun part. Open the Device manager by right-clicking the Windows button and selecting &lt;code&gt;Device Manager&lt;/code&gt; and, one by one, open the property pages for the devices listed above. In the &lt;code&gt;Power management&lt;/code&gt; tab, you&#39;ll find a flag that you want to disable: &lt;code&gt;Allow this device to wake the computer&lt;/code&gt;. Do so for each device. For your network card, you can optionally set it so that only a &amp;quot;magic packet&amp;quot; can wake up the computer. If you don&#39;t know what a magic packet is, it&#39;s safe to disable it 😅&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://codemade.net/images/badkeyboard.png&quot; alt=&quot;Power options for a keyboard in Windows device manager&quot;&gt;&lt;/p&gt;
&lt;h2&gt;4: Find out if any scheduled tasks are waking up your computer&lt;/h2&gt;
&lt;p&gt;If the above steps didn&#39;t work, it&#39;s time to bring out the big guns. And by big guns I mean this powershell command: &lt;code&gt;Get-ScheduledTask | where {$_.settings.waketorun}&lt;/code&gt;. It will list all the scheduled tasks that are allowed to wake up your computer in order to perform some maintenance, or more likely to install updates:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;❯ Get-ScheduledTask | where {$_.settings.waketorun}

TaskPath                                       TaskName                          State
--------                                       --------                          -----
&#92;Microsoft&#92;Windows&#92;.NET Framework&#92;             .NET Framework NGEN v4.0.30319... Disabled
&#92;Microsoft&#92;Windows&#92;.NET Framework&#92;             .NET Framework NGEN v4.0.30319... Disabled
&#92;Microsoft&#92;Windows&#92;InstallService&#92;             WakeUpAndContinueUpdates          Disabled
&#92;Microsoft&#92;Windows&#92;InstallService&#92;             WakeUpAndScanForUpdates           Disabled
&#92;Microsoft&#92;Windows&#92;SharedPC&#92;                   Account Cleanup                   Disabled
&#92;Microsoft&#92;Windows&#92;UpdateAssistant&#92;            UpdateAssistantWakeupRun          Disabled
&#92;Microsoft&#92;Windows&#92;UpdateOrchestrator&#92;         Backup Scan                       Ready
&#92;Microsoft&#92;Windows&#92;UpdateOrchestrator&#92;         Reboot                            Ready
&#92;Microsoft&#92;Windows&#92;UpdateOrchestrator&#92;         Reboot_AC                         Disabled
&#92;Microsoft&#92;Windows&#92;UpdateOrchestrator&#92;         Universal Orchestrator Start      Ready
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In my case, the &amp;quot;Backup Scan&amp;quot; task above was the culprit, so I disabled it. You could disable it by opening the Windows task scheduler, but sometimes the UI won&#39;t allow you to disable one of the &amp;quot;system&amp;quot; scheduled tasks like the ones related to Windows Update. But a simple powershell command can take care of it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;❯ Disable-ScheduledTask  &amp;quot;&#92;Microsoft&#92;Windows&#92;UpdateOrchestrator&#92;Backup Scan&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check again with the previous command, and it will be disabled:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;❯ Get-ScheduledTask | where {$_.settings.waketorun}

TaskPath                                       TaskName                          State
--------                                       --------                          -----
&#92;Microsoft&#92;Windows&#92;.NET Framework&#92;             .NET Framework NGEN v4.0.30319... Disabled
&#92;Microsoft&#92;Windows&#92;.NET Framework&#92;             .NET Framework NGEN v4.0.30319... Disabled
&#92;Microsoft&#92;Windows&#92;InstallService&#92;             WakeUpAndContinueUpdates          Disabled
&#92;Microsoft&#92;Windows&#92;InstallService&#92;             WakeUpAndScanForUpdates           Disabled
&#92;Microsoft&#92;Windows&#92;SharedPC&#92;                   Account Cleanup                   Disabled
&#92;Microsoft&#92;Windows&#92;UpdateAssistant&#92;            UpdateAssistantWakeupRun          Disabled
&#92;Microsoft&#92;Windows&#92;UpdateOrchestrator&#92;         Backup Scan                       Disabled
&#92;Microsoft&#92;Windows&#92;UpdateOrchestrator&#92;         Reboot                            Ready
&#92;Microsoft&#92;Windows&#92;UpdateOrchestrator&#92;         Reboot_AC                         Disabled
&#92;Microsoft&#92;Windows&#92;UpdateOrchestrator&#92;         Universal Orchestrator Start      Ready
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now hopefully you can set your device to sleep mode and have some well deserved, uninterrupted sleep yourself! 😁&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;small&gt;&lt;em&gt;The feature image above was taken by &lt;a href=&quot;https://unsplash.com/@cbpsc1&quot;&gt;Clint Patterson&lt;/a&gt;&lt;/em&gt;&lt;/small&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>How to combine Docker and VSCode to reach development nirvana</title>
    <link href="https://codemade.net/blog/devcontainers/" />
    <updated>2020-10-10T00:00:00Z</updated>
    <id>https://codemade.net/blog/devcontainers/</id>
    <content type="html">&lt;p&gt;One of the best features in VS Code, and my personal favourite is called &lt;a href=&quot;https://code.visualstudio.com/docs/remote/containers&quot;&gt;Remote Containers&lt;/a&gt;.
What it does is open the folder you&#39;re working on &lt;em&gt;inside&lt;/em&gt; a container and then connect to it, so that you can, for example, run your jekyll site on windows without having to install ruby system-wide. Because all the development is done inside the container, you don&#39;t have to worry about having multiple versions of node or python cluttering your machine.&lt;/p&gt;
&lt;p&gt;This is especially useful if your main OS is Windows, as getting set up with some languages is notoriously tougher there.&lt;/p&gt;
&lt;p&gt;Working in a devcontainer has also the advantage that all the settings can be committed into source control, so you can share the exact same development environment with the rest of your team. No more 15-step procedure to follow to set up the local dev environment!&lt;/p&gt;
&lt;p&gt;Getting started is really simple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Install the &lt;code&gt;Remote - Containers&lt;/code&gt; extension&lt;/li&gt;
&lt;li&gt;Press F1 to open the command palette&lt;/li&gt;
&lt;li&gt;Run &amp;quot;Open folder in container&amp;quot; command&lt;/li&gt;
&lt;li&gt;Select &amp;quot;From a predefined container definition&amp;quot;&lt;/li&gt;
&lt;li&gt;Select the most appropriate container image.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This will create:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;.devcontainer&lt;/code&gt; folder in your project, containing:&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;dockerfile&lt;/code&gt; for the development container&lt;/li&gt;
&lt;li&gt;A file named &lt;code&gt;devcontainer.json&lt;/code&gt; that is mostly used to define any ports that should be forwarded to the host&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can now open the integrated terminal and type commands directly in your container!&lt;/p&gt;
&lt;h2&gt;An example: this website&lt;/h2&gt;
&lt;p&gt;I love the simplicity that comes with static site generators, and &lt;a href=&quot;https://jekyllrb.com&quot;&gt;jekyll&lt;/a&gt; is by far the most popular choice. Using a development container setup allows me to edit and test any change on all my computers, without having to install ruby, bundler or any of jekyll&#39;s dependencies.&lt;/p&gt;
&lt;p&gt;This is the dockerfile I use for my jekyll devcontainer. It&#39;s a slightly modified version based on the official Microsoft one from some time ago; I had to add a line to install &lt;code&gt;zlib1g-dev&lt;/code&gt; otherwise it wouldn&#39;t work 😅.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/LBognanni/ed91a3b29089b85a23e67bbdafaa10c2&quot;&gt;See the dockerfile on github&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Now, when I open the repository in Visual studio code, I just have to run &lt;code&gt;bundle exec jekyll serve&lt;/code&gt; in the integrated terminal to run it. The container exposes port 4000 so I can just open http://localhost:4000 in my browser and preview the changes.
What&#39;s more, if port 4000 is already used by another program, when clicking on the localhost link in the terminal, VS Code will transparently open the site on a different port!&lt;/p&gt;
&lt;p&gt;Found this useful? Want to add something? &lt;a href=&quot;https://twitter.com/lorisdev&quot;&gt;Reach out on twitter&lt;/a&gt;!&lt;/p&gt;
</content>
  </entry>
</feed>