<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: JLarky</title>
    <description>The latest articles on DEV Community by JLarky (@jlarky).</description>
    <link>https://dev.to/jlarky</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F175913%2F8b5d6193-fed0-4739-b823-d5d50710e9fc.png</url>
      <title>DEV Community: JLarky</title>
      <link>https://dev.to/jlarky</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jlarky"/>
    <language>en</language>
    <item>
      <title>Running a Local Sandboxed macOS Desktop Using VNC and a Restricted User</title>
      <dc:creator>JLarky</dc:creator>
      <pubDate>Mon, 09 Mar 2026 23:47:06 +0000</pubDate>
      <link>https://dev.to/jlarky/running-a-local-sandboxed-macos-desktop-using-vnc-and-a-restricted-user-38dk</link>
      <guid>https://dev.to/jlarky/running-a-local-sandboxed-macos-desktop-using-vnc-and-a-restricted-user-38dk</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9r6z4t27h1p9e0rqwxk9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9r6z4t27h1p9e0rqwxk9.png" alt="demo" width="800" height="546"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sometimes you want to run commands that you &lt;strong&gt;don’t fully trust&lt;/strong&gt; or that intentionally bypass safeguards. A good example is running AI agents like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude &lt;span class="nt"&gt;--dangerously-skip-permissions&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that command executes arbitrary shell commands, writes files, or installs software, running it in your main admin session is risky.&lt;/p&gt;

&lt;p&gt;A lightweight alternative to a full VM is to create a &lt;strong&gt;separate macOS user session&lt;/strong&gt; and connect to it via &lt;strong&gt;VNC in a window&lt;/strong&gt;. This gives you a &lt;strong&gt;local desktop sandbox&lt;/strong&gt; with minimal setup.&lt;/p&gt;

&lt;p&gt;No virtualization required.&lt;/p&gt;

&lt;p&gt;The architecture looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Admin user
   ↓
Screen Sharing (VNC)
   ↓
Restricted macOS user
   ↓
Run risky commands safely
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why This Works Well for Developers
&lt;/h2&gt;

&lt;p&gt;This setup provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;separate &lt;code&gt;$HOME&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;separate login keychain&lt;/li&gt;
&lt;li&gt;separate permissions&lt;/li&gt;
&lt;li&gt;no admin privileges&lt;/li&gt;
&lt;li&gt;easy reset by deleting the user&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You also get a &lt;strong&gt;windowed sandbox desktop&lt;/strong&gt;, similar to a VM but much lighter.&lt;/p&gt;

&lt;p&gt;This is ideal for things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;running &lt;code&gt;claude --dangerously-skip-permissions&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;testing install scripts&lt;/li&gt;
&lt;li&gt;experimenting with unknown npm packages&lt;/li&gt;
&lt;li&gt;isolating automation tools&lt;/li&gt;
&lt;li&gt;testing shell agents&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1 — Create a Restricted User
&lt;/h2&gt;

&lt;p&gt;Open:&lt;/p&gt;

&lt;p&gt;System Settings → Users &amp;amp; Groups&lt;/p&gt;

&lt;p&gt;Add a new user:&lt;/p&gt;

&lt;p&gt;Account Type: Standard&lt;br&gt;
Name: sandbox&lt;/p&gt;

&lt;p&gt;Important: &lt;strong&gt;Do NOT make it an admin user.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This ensures the sandbox cannot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;install system software&lt;/li&gt;
&lt;li&gt;modify system settings&lt;/li&gt;
&lt;li&gt;escalate privileges easily&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Step 2 — Enable Screen Sharing
&lt;/h2&gt;

&lt;p&gt;Open:&lt;/p&gt;

&lt;p&gt;System Settings → General → Sharing&lt;/p&gt;

&lt;p&gt;Enable:&lt;/p&gt;

&lt;p&gt;Screen Sharing&lt;/p&gt;

&lt;p&gt;Click the &lt;strong&gt;ⓘ info button&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Set access to:&lt;/p&gt;

&lt;p&gt;Allow access for: Only these users&lt;/p&gt;

&lt;p&gt;Add your &lt;strong&gt;restricted user&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;sandbox&lt;/p&gt;

&lt;p&gt;This ensures &lt;strong&gt;only that account can initiate screen sharing sessions.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 3 — Log Into the Sandbox User
&lt;/h2&gt;

&lt;p&gt;Enable &lt;strong&gt;Fast User Switching&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;System Settings → Control Center&lt;br&gt;
→ Fast User Switching&lt;br&gt;
→ Show in Menu Bar&lt;/p&gt;

&lt;p&gt;Then:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click your username in the menu bar&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Login Window&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Log in as the &lt;code&gt;sandbox&lt;/code&gt; user&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The sandbox session is now running in the background.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 4 — Connect to the Sandbox Desktop
&lt;/h2&gt;

&lt;p&gt;macOS normally blocks connecting to your own screen and shows:&lt;/p&gt;

&lt;p&gt;You cannot control your own screen&lt;/p&gt;

&lt;p&gt;To bypass this, create a &lt;strong&gt;local port forward&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-NL&lt;/span&gt; 5901:localhost:5900 localhost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This forwards:&lt;/p&gt;

&lt;p&gt;localhost:5901 → localhost:5900&lt;/p&gt;

&lt;p&gt;Now connect using Finder.&lt;/p&gt;

&lt;p&gt;Press:&lt;/p&gt;

&lt;p&gt;⌘ + K&lt;/p&gt;

&lt;p&gt;Enter:&lt;/p&gt;

&lt;p&gt;vnc://localhost:5901&lt;/p&gt;

&lt;p&gt;This opens the &lt;strong&gt;Screen Sharing app in a window&lt;/strong&gt; connected to the sandbox desktop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix for “You Cannot Control Your Own Screen”
&lt;/h2&gt;

&lt;p&gt;If you try connecting directly to &lt;code&gt;vnc://localhost&lt;/code&gt;, macOS will block it.&lt;/p&gt;

&lt;p&gt;The SSH tunnel above solves this issue.&lt;/p&gt;

&lt;p&gt;See the StackExchange discussion explaining the workaround:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apple.stackexchange.com/questions/151151/can-i-remote-desktop-to-another-user-on-the-same-machine" rel="noopener noreferrer"&gt;https://apple.stackexchange.com/questions/151151/can-i-remote-desktop-to-another-user-on-the-same-machine&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 — Run Your Risky Commands
&lt;/h2&gt;

&lt;p&gt;Inside the sandbox desktop window you can now safely run things like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude &lt;span class="nt"&gt;--dangerously-skip-permissions&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even if the agent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;modifies files&lt;/li&gt;
&lt;li&gt;installs packages&lt;/li&gt;
&lt;li&gt;writes scripts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;it will only affect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/Users/sandbox
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your main development environment stays safe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Convenience Shortcut
&lt;/h2&gt;

&lt;p&gt;To open the sandbox quickly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;open vnc://localhost:5901
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can even create a shell alias:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;sandbox&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"open vnc://localhost:5901"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now launch the sandbox with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sandbox
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  ⚠️ Security Caveat
&lt;/h2&gt;

&lt;p&gt;A separate macOS user is &lt;strong&gt;not a strong security sandbox&lt;/strong&gt;. It helps prevent accidental damage but does not fully isolate data.&lt;/p&gt;

&lt;p&gt;Files in your main home directory that are &lt;strong&gt;world-readable&lt;/strong&gt; can still be accessed by the sandbox user.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/Users/realUser/.aws/config
/Users/realUser/.aws/credentials
~/.gitconfig
~/.npmrc
~/.env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If permissions look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-rw-r--r--
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then any user on the machine can read them.&lt;/p&gt;

&lt;p&gt;For instance, the sandbox user could run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /Users/realUser/.aws/config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Many tools lock down sensitive files automatically, but not all do.&lt;/p&gt;

&lt;p&gt;You can audit files that other users can read:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find ~ &lt;span class="nt"&gt;-perm&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt;+r
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And tighten permissions where needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;600 ~/.aws/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;span class="nb"&gt;chmod &lt;/span&gt;600 ~/.ssh/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This setup protects against &lt;strong&gt;accidental breakage&lt;/strong&gt;, but it is not meant to contain &lt;strong&gt;malicious software&lt;/strong&gt;. If you need stronger isolation, use a VM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advantages Over Virtual Machines
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;This Setup&lt;/th&gt;
&lt;th&gt;VM&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;RAM usage&lt;/td&gt;
&lt;td&gt;very low&lt;/td&gt;
&lt;td&gt;high&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Startup time&lt;/td&gt;
&lt;td&gt;instant&lt;/td&gt;
&lt;td&gt;slow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disk usage&lt;/td&gt;
&lt;td&gt;minimal&lt;/td&gt;
&lt;td&gt;large&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Native macOS apps&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;limited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hardware acceleration&lt;/td&gt;
&lt;td&gt;full&lt;/td&gt;
&lt;td&gt;partial&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For many developer workflows, this &lt;strong&gt;feels like a lightweight local VM&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Result
&lt;/h2&gt;

&lt;p&gt;Your system now looks like:&lt;/p&gt;

&lt;p&gt;Admin Desktop&lt;br&gt;
↓&lt;br&gt;
Sandbox Window (VNC)&lt;br&gt;
↓&lt;br&gt;
Restricted macOS user&lt;br&gt;
↓&lt;br&gt;
Run risky tools safely&lt;/p&gt;

&lt;p&gt;A simple, fast, and effective way to isolate powerful developer tools.&lt;/p&gt;

</description>
      <category>abotwrotethis</category>
      <category>sandbox</category>
      <category>security</category>
    </item>
    <item>
      <title>Modern Ways to Tame GitHub Action Workflows</title>
      <dc:creator>JLarky</dc:creator>
      <pubDate>Mon, 20 Oct 2025 23:07:45 +0000</pubDate>
      <link>https://dev.to/jlarky/modern-ways-to-tame-github-action-workflows-4006</link>
      <guid>https://dev.to/jlarky/modern-ways-to-tame-github-action-workflows-4006</guid>
      <description>&lt;p&gt;If you’ve ever cracked open a &lt;code&gt;.github/workflows&lt;/code&gt; folder, you probably felt that mix of resignation and dread that only YAML can inspire. Indentation errors, random &lt;code&gt;on:&lt;/code&gt; vs &lt;code&gt;jobs:&lt;/code&gt; confusion, the joy of debugging a misaligned dash — truly a rite of passage. If you’re lucky, GitHub Actions is the &lt;em&gt;only&lt;/em&gt; place in your stack where you still have to deal with YAML on a daily basis. For the rest of us, it’s an ever-present reminder that whitespace is a cruel and unforgiving god.&lt;/p&gt;

&lt;p&gt;Thankfully, we don’t &lt;em&gt;have&lt;/em&gt; to hand-craft every workflow file anymore. You can generate them, reuse them, or even visually design them — the YAML just happens to be the final delivery format. GitHub itself has been improving the situation: first with &lt;strong&gt;reusable workflows&lt;/strong&gt; and &lt;strong&gt;composite actions&lt;/strong&gt;, and more recently by adding &lt;strong&gt;YAML anchors&lt;/strong&gt; (&lt;a href="https://github.blog/changelog/2025-09-18-actions-yaml-anchors-and-non-public-workflow-templates/" rel="noopener noreferrer"&gt;finally&lt;/a&gt;!) to let you reuse sections within the same file. Still, those features only go so far — you can’t run a loop or type-check your inputs, and good luck spotting a missing field until your CI job fails.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Landscape: Lint, Generate, or Visualize
&lt;/h2&gt;

&lt;p&gt;There are three main schools of thought when it comes to keeping your sanity with GitHub Actions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Linting and validation.&lt;/strong&gt;&lt;br&gt;
Tools like &lt;a href="https://github.com/rhysd/actionlint" rel="noopener noreferrer"&gt;actionlint&lt;/a&gt; help you catch structural mistakes before you push your code. They parse and validate your YAML files, ensuring field names, indentation, and job references are valid — basically a CI spellchecker. If you’re living inside VS Code, the &lt;a href="https://marketplace.visualstudio.com/items?itemName=GitHub.vscode-github-actions" rel="noopener noreferrer"&gt;GitHub Actions extension&lt;/a&gt; provides autocomplete, schema validation, and instant red squiggles for common errors. Linting doesn’t remove YAML from your life, but it makes it less of a horror movie. Think of this as adding bumpers to your YAML bowling lane — you’ll still hit the walls, but fewer builds will end up in CI jail.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Generation and code-as-config.&lt;/strong&gt;&lt;br&gt;
This camp believes the best YAML is the one you never have to write. Instead of treating workflows as text files, you define them in a real programming language like TypeScript, Kotlin, or C# and compile them to YAML. This gives you static types, autocompletion, loops, variables, and the ability to share workflow components as normal code modules. It’s configuration as code — literally. You can create a single “build pipeline” function and reuse it across dozens of repos, and your editor will help you refactor it safely. The YAML output is still there for GitHub to consume, but it’s now just a build artifact, not the source of truth.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Visual tools and builders.&lt;/strong&gt;&lt;br&gt;
A newer breed of tools aims to make workflow authoring accessible even to non-developers. Visual editors like &lt;strong&gt;Actionforge&lt;/strong&gt; let you drag, drop, and connect boxes representing jobs and steps. They handle the YAML generation for you and even enable logic that plain YAML doesn’t support — such as loops, conditionals, or branches. These are especially useful in large orgs where not every team wants to read YAML syntax diagrams just to tweak a test matrix.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Tooling in the Wild
&lt;/h2&gt;

&lt;p&gt;The “YAML escape plan” ecosystem is surprisingly diverse, spanning from strongly typed DSLs to visual editors. What unites them is the goal of making workflows &lt;em&gt;less fragile&lt;/em&gt; and &lt;em&gt;more maintainable&lt;/em&gt; — each taking a slightly different path toward that end.&lt;/p&gt;

&lt;p&gt;The most common approach right now is &lt;strong&gt;generation via a general-purpose language&lt;/strong&gt;. Here, developers write workflows in TypeScript, Kotlin, or C#, relying on familiar tooling like type checking and linting to ensure correctness. These solutions treat CI pipelines the way frontend developers treat CSS: you don’t edit the generated file — you write the higher-level definition and let the compiler handle the messy bits. For large teams, this means workflows can evolve safely, reviewed and versioned just like application code.&lt;/p&gt;

&lt;p&gt;Meanwhile, &lt;strong&gt;visual tools&lt;/strong&gt; have opened the door for teams who prefer to reason about pipelines visually rather than textually. The node-graph interface of Actionforge, for instance, makes it possible to express complex logic with minimal YAML knowledge — a huge win for onboarding and accessibility. At the same time, the YAML remains fully exportable, so teams aren’t locked in if they ever want to switch back.&lt;/p&gt;

&lt;p&gt;Finally, &lt;strong&gt;linting and IDE integration&lt;/strong&gt; form the safety net for everyone else. Even if you stick to plain YAML, actionlint and the official VS Code GitHub Actions extension offer quick feedback loops that save hours of trial-and-error debugging. Combined with GitHub’s recent addition of YAML anchors and reusable workflows, they give YAML authors some of the modularity and reuse long enjoyed by code-based alternatives.&lt;/p&gt;




&lt;h3&gt;
  
  
  A Few Standout Tools
&lt;/h3&gt;

&lt;p&gt;Here are a few of the most active projects tackling the “YAML fatigue” problem, roughly ordered by GitHub stars:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/typesafegithub/github-workflows-kt" rel="noopener noreferrer"&gt;github-workflows-kt&lt;/a&gt;&lt;/strong&gt; (★630+) – A mature Kotlin DSL for GitHub Actions. You write workflows in strongly typed Kotlin and compile to YAML. Excellent type safety, powerful IDE support, and a thriving community.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/koki-develop/ghats" rel="noopener noreferrer"&gt;ghats&lt;/a&gt;&lt;/strong&gt; (★130+) – A TypeScript-based workflow generator that aims for simplicity and predictable YAML output. Ideal for teams already using Node tooling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/JLarky/gha-ts" rel="noopener noreferrer"&gt;gha-ts&lt;/a&gt;&lt;/strong&gt; (★110+) – A lightweight TypeScript library with deterministic generation and strong typing. It focuses on the developer experience of defining workflows as real code while keeping YAML output transparent and review-friendly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/actionforge/vscode-ext" rel="noopener noreferrer"&gt;Actionforge&lt;/a&gt;&lt;/strong&gt; (★75+) – A VS Code extension that replaces YAML editing with a node-based visual interface, including branching and looping that standard YAML can’t do.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these tools attacks the same pain point from a different angle. Whether you prefer Kotlin’s strict typing, TypeScript’s developer ergonomics, or Actionforge’s drag-and-drop design, the end goal is the same — reducing the human error surface of YAML while keeping your CI pipelines consistent and DRY.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bigger Question
&lt;/h2&gt;

&lt;p&gt;Wouldn’t it be cool if GitHub just let us use &lt;em&gt;something other than YAML&lt;/em&gt; to define workflows? GitLab, Buildkite, and others already allow pipelines in JSON, HCL, or even full programming languages. GitHub Actions is incredibly powerful, but its YAML-only approach increasingly feels like a relic of simpler times.&lt;/p&gt;

&lt;p&gt;Until that changes, we’ll keep finding clever ways to wrap, lint, or generate our workflows. Whether you choose reusable workflows, anchors, or a DSL in your favorite language, the mission remains the same: spend less time fixing indentation and more time shipping code.&lt;/p&gt;




</description>
      <category>programming</category>
      <category>githubactions</category>
      <category>abotwrotethis</category>
    </item>
    <item>
      <title>Combine Multiple Repos Into One Monorepo With One Root package-lock.json (Step-by-Step Guide)</title>
      <dc:creator>JLarky</dc:creator>
      <pubDate>Tue, 14 Oct 2025 00:17:53 +0000</pubDate>
      <link>https://dev.to/jlarky/unifying-multiple-apps-into-one-monorepo-with-one-root-2jko</link>
      <guid>https://dev.to/jlarky/unifying-multiple-apps-into-one-monorepo-with-one-root-2jko</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;If you’ve ever tried to merge multiple Node.js or frontend apps into a single monorepo and struggled with conflicting package-lock.json files, this guide explains how to unify them under one root using NPM workspaces — with scripts to verify nothing breaks.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Unifying Multiple Apps Into One Monorepo With a Single Root Lockfile (npm Workspaces)
&lt;/h3&gt;

&lt;p&gt;This article explains how to bring multiple independent applications into a single monorepo that uses one shared npm workspace and one root lockfile. It focuses on the practical approach: unify under one root workspace, let npm re-resolve the dependency graph, and validate the result with a simple diff of dependency trees. Hoisting, inter-app dependencies, publishing/versioning, deployment, engine/platform details, and governance are out of scope.&lt;/p&gt;

&lt;h3&gt;
  
  
  Who this is for
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Audience&lt;/strong&gt;: Engineers running large monorepos with multiple separately-evolved applications who want deterministic installs and a single source of truth for dependencies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prerequisites&lt;/strong&gt;: Familiarity with npm workspaces and lockfiles. We’ll use npm; the concepts map to pnpm/Yarn with different lockfile shapes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Problem statement
&lt;/h3&gt;

&lt;p&gt;Multiple applications evolved in separate repositories with their own &lt;code&gt;package.json&lt;/code&gt; and &lt;code&gt;package-lock.json&lt;/code&gt;. When consolidating them into one monorepo, keeping per-app lockfiles introduces drift and non-determinism. We want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One root workspace&lt;/strong&gt; (root &lt;code&gt;package.json&lt;/code&gt; with &lt;code&gt;workspaces&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One root lockfile&lt;/strong&gt; (&lt;code&gt;package-lock.json&lt;/code&gt; at repo root)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic installs&lt;/strong&gt; via &lt;code&gt;npm install&lt;/code&gt; at the repository root only&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A way to validate&lt;/strong&gt; that each app’s dependency tree stays as intended across the migration&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Approach (high level)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unify under a single workspace&lt;/strong&gt;: Move apps into the repo and declare them in the root &lt;code&gt;workspaces&lt;/code&gt; array.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Merge lockfile state&lt;/strong&gt; conceptually: Bring per-app lockfile information under a workspace-scoped namespace in the root lockfile (helper script optional), then reinstall at the root and let npm compute the final graph.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate&lt;/strong&gt;: Snapshot each app’s dependency tree before and after the merge and compare.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Target workspace layout
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my-monorepo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"private"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"workspaces"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"apps/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"packages/*"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example file tree:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├─ package.json                # root workspace definition (single source of truth)
├─ package-lock.json           # unified root lockfile
├─ apps/
│  ├─ app-a/
│  │  ├─ package.json
│  │  └─ ...
│  └─ app-b/
│     ├─ package.json
│     └─ ...
└─ packages/                   # optional shared libraries
   └─ ui-components/
      ├─ package.json
      └─ ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Migration flow (end-to-end)
&lt;/h3&gt;

&lt;p&gt;1) &lt;strong&gt;Move apps into the monorepo&lt;/strong&gt; under &lt;code&gt;apps/&amp;lt;name&amp;gt;&lt;/code&gt; with their own &lt;code&gt;package.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;2) &lt;strong&gt;Configure root workspace&lt;/strong&gt; by listing apps in root &lt;code&gt;package.json&lt;/code&gt; -&amp;gt; &lt;code&gt;workspaces&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;3) &lt;strong&gt;Snapshot each app’s dependency tree (before)&lt;/strong&gt; to establish a baseline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;apps/app-a &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;--all&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; versions-before.txt
&lt;span class="nb"&gt;cd&lt;/span&gt; ../../
&lt;span class="nb"&gt;cd &lt;/span&gt;apps/app-b &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;--all&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; versions-before.txt
&lt;span class="nb"&gt;cd&lt;/span&gt; ../../
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;4) &lt;strong&gt;Converge to a single root lockfile&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Preferred: run &lt;code&gt;npm install&lt;/code&gt; at the repo root to let npm compute the unified graph.&lt;/li&gt;
&lt;li&gt;Optional helper: a script that conceptually folds per-app lockfile entries into the root lockfile under &lt;code&gt;apps/&amp;lt;app-name&amp;gt;/...&lt;/code&gt; prior to the reinstall. This can reduce noisy diffs and make the transition easier to reason about.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;5) &lt;strong&gt;Reinstall at the root&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;--all&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; versions-after.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;6) &lt;strong&gt;Compare per-app trees (after vs. before)&lt;/strong&gt; using a small diff script. Fail CI if unwanted changes are detected.&lt;/p&gt;

&lt;p&gt;The rest of this article focuses on two small scripts you can adopt or adapt:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;merge-to-root.ts&lt;/code&gt; (optional helper): conceptually folds a workspace lockfile into the root lockfile before reinstalling.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;diff-workspace-deps.ts&lt;/code&gt;: compares an app’s “before” vs “after” dependency trees.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Optional merge helper: &lt;code&gt;merge-to-root.ts&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Purpose: A pragmatic helper if you want to seed the root lockfile with each app’s lockfile content (namespaced under the workspace path) before running the root &lt;code&gt;npm install&lt;/code&gt;. This is not strictly required; you can skip this entire step and rely solely on &lt;code&gt;npm install&lt;/code&gt; at the root to produce the unified lockfile.&lt;/p&gt;

&lt;p&gt;Conceptually, the script does three things for each workspace you fold in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reads the root &lt;code&gt;package-lock.json&lt;/code&gt; and the app’s &lt;code&gt;package-lock.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Copies the app’s lockfile entries into the root lockfile under a namespaced key like &lt;code&gt;apps/app-a/node_modules/...&lt;/code&gt; and creates a top-level &lt;code&gt;packages["apps/app-a"]&lt;/code&gt; entry with the app’s own metadata.&lt;/li&gt;
&lt;li&gt;Writes the updated root lockfile; you then run &lt;code&gt;npm install&lt;/code&gt; at the root to let npm reconcile.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example (illustrative, partial — adapt as needed):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// scripts/merge-to-root.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;writeFile&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:fs/promises&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;readJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Json&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;writeJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;\n`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;writeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;namespacePackages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;packages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;packages&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// handled separately for workspace root entry&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node_modules&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// leave other keys as-is (e.g., other workspaces already present)&lt;/span&gt;
      &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;foldWorkspaceIntoRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;rootLockfile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;workspaceLockfile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;workspaceDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// e.g., 'apps/app-a'&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Json&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rootPackages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rootLockfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;packages&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wsPackages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workspaceLockfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;packages&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Create a namespaced view of the workspace's node_modules entries&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;namespaced&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;namespacePackages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wsPackages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;workspaceDir&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Copy namespaced entries into the root map&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mergedPackages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;rootPackages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;namespaced&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;// Create the workspace's top-level entry (metadata from the workspace lockfile root "" entry)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wsRoot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;wsPackages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
  &lt;span class="nx"&gt;mergedPackages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;workspaceDir&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;wsRoot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;wsRoot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;devDependencies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;wsRoot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;devDependencies&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;engines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;wsRoot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;engines&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;rootLockfile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mergedPackages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;workspaceName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// e.g., 'app-a'&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;workspaceName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Usage: bun ts scripts/merge-to-root.ts &amp;lt;workspaceName&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;workspaceDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;apps&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;workspaceName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rootLockPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;package-lock.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wsLockPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workspaceDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;package-lock.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;rootLock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;wsLock&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nf"&gt;readJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rootLockPath&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;readJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wsLockPath&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;merged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;foldWorkspaceIntoRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rootLock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;wsLock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;workspaceDir&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;writeJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rootLockPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Seeded &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;workspaceName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; into root lockfile. Now run: npm install`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Usage examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Seed app-a into the root lockfile (optional helper), then reinstall&lt;/span&gt;
bun ts scripts/merge-to-root.ts app-a
npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This helper’s goal is to reduce churn and make the transition predictable. It is not a generic, lossless lockfile merger; npm’s resolver remains the source of truth once you run &lt;code&gt;npm install&lt;/code&gt; at the root.&lt;/li&gt;
&lt;li&gt;If you prefer not to seed, skip this step and rely solely on &lt;code&gt;npm install&lt;/code&gt; at the root.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Dependency diff helper: &lt;code&gt;diff-workspace-deps.ts&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Purpose: Compare an individual workspace’s dependency tree before vs. after unification. It reads &lt;code&gt;npm ls --all&lt;/code&gt; output and reports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CHANGED (packages whose version set changed)&lt;/li&gt;
&lt;li&gt;ADDED (new &lt;code&gt;name@version&lt;/code&gt; combos)&lt;/li&gt;
&lt;li&gt;REMOVED (removed &lt;code&gt;name@version&lt;/code&gt; combos)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Design:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Before” file is captured inside the app (when it was standalone or before unification): &lt;code&gt;apps/app-a/versions-before.txt&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;“After” file is captured at the repo root after &lt;code&gt;npm install&lt;/code&gt;: &lt;code&gt;versions-after.txt&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The parser extracts the dependency subtree for the specific workspace from the monorepo-level &lt;code&gt;npm ls&lt;/code&gt; tree by detecting the &lt;code&gt;app-a@&amp;lt;version&amp;gt; -&amp;gt; ./apps/app-a&lt;/code&gt; block and collecting lines until indentation returns to the root.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example (illustrative, partial):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// scripts/diff-workspace-deps.ts&lt;/span&gt;
&lt;span class="cm"&gt;/* eslint-disable no-console */&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;readFile&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:fs/promises&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;VersionSetMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;((?:&lt;/span&gt;&lt;span class="sr"&gt;@&lt;/span&gt;&lt;span class="se"&gt;[^\/\s]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;\/)?[&lt;/span&gt;&lt;span class="sr"&gt;A-Za-z0-9._-&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;@&lt;/span&gt;&lt;span class="se"&gt;([&lt;/span&gt;&lt;span class="sr"&gt;0-9&lt;/span&gt;&lt;span class="se"&gt;][^\s]&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;parseBefore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;workspaceName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;VersionSetMap&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;VersionSetMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rootRe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`^&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;workspaceName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;@[0-9]`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rootRe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// skip the root line like "app-a@1.2.3 /path"&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nv&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;nv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;workspaceName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;parseAfter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;workspaceName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;VersionSetMap&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Example monorepo tree line: "└─┬ app-a@1.2.3 -&amp;gt; ./apps/app-a"&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rootRe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`^[├└]─[┬─]\s+&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;workspaceName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;@`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;inBlock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;VersionSetMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;inBlock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rootRe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nx"&gt;inBlock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// Stop when indentation returns to the repo root&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;│&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nv&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;nv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;workspaceName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;compare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;before&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;VersionSetMap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;after&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;VersionSetMap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;names&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;([...&lt;/span&gt;&lt;span class="nx"&gt;before&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;after&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;()]);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;combos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;VersionSetMap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;vs&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;vs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;@&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bCombos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;combos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;before&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;aCombos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;combos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;after&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;added&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;aCombos&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;bCombos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;removed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;bCombos&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;aCombos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;names&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...(&lt;/span&gt;&lt;span class="nx"&gt;before&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;())].&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...(&lt;/span&gt;&lt;span class="nx"&gt;after&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;())].&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;eq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;every&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;eq&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nx"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: [&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;] -&amp;gt; [&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;]`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;added&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;removed&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;workspaceName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;beforeArg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;apps&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;workspaceName&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;versions-before.txt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;afterArg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;versions-after.txt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;workspaceName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Usage: bun ts scripts/diff-workspace-deps.ts &amp;lt;workspaceName&amp;gt; [beforeFile] [afterFile]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;beforeContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;afterContent&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nf"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;beforeArg&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;afterArg&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;beforeMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseBefore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;beforeContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;workspaceName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;afterMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseAfter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;afterContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;workspaceName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;added&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;removed&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;compare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;beforeMap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;afterMap&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CHANGED (by package)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;ADDED (name@version)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;added&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;added&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;REMOVED (name@version)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;removed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;removed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;Dependency changes detected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Usage examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Generate snapshots&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;apps/app-a &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;--all&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; versions-before.txt &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; -
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;--all&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; versions-after.txt

&lt;span class="c"&gt;# Diff app-a only&lt;/span&gt;
bun ts scripts/diff-workspace-deps.ts app-a apps/app-a/versions-before.txt versions-after.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What to do with results:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Empty “CHANGED/ADDED/REMOVED” means your app’s dependency tree stayed equivalent.&lt;/li&gt;
&lt;li&gt;Non-empty output means your unified install altered the app’s resolved dependencies. Decide whether to accept or adjust before proceeding.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  End-to-end example (bringing two apps into one root lockfile)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1) Add apps to the monorepo under apps/&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; apps/app-a apps/app-b
&lt;span class="c"&gt;# (copy in each app's package.json and source)&lt;/span&gt;

&lt;span class="c"&gt;# 2) Configure the root workspace (package.json workspaces)&lt;/span&gt;
&lt;span class="c"&gt;#    Then optionally install in each app and snapshot "before"&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;apps/app-a &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;--all&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; versions-before.txt &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; -
&lt;span class="nb"&gt;cd &lt;/span&gt;apps/app-b &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;--all&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; versions-before.txt &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; -

&lt;span class="c"&gt;# 3) (Optional) Seed app-a into the root lockfile to reduce churn&lt;/span&gt;
bun ts scripts/merge-to-root.ts app-a

&lt;span class="c"&gt;# 4) Install once at the root to produce a unified lockfile&lt;/span&gt;
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;--all&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; versions-after.txt

&lt;span class="c"&gt;# 5) Validate each app&lt;/span&gt;
bun ts scripts/diff-workspace-deps.ts app-a apps/app-a/versions-before.txt versions-after.txt
bun ts scripts/diff-workspace-deps.ts app-b apps/app-b/versions-before.txt versions-after.txt

&lt;span class="c"&gt;# Use exit codes to gate CI: non-zero indicates changes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Notes on npm vs pnpm/Yarn
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;approach&lt;/strong&gt; (single workspace, reinstall to reconcile, validate via diffs) generalizes.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;lockfile structure&lt;/strong&gt; differs. If you adapt the optional merge helper, update parsing/keys accordingly.&lt;/li&gt;
&lt;li&gt;If you skip the merge helper and rely on &lt;code&gt;npm install&lt;/code&gt; at the root, the process is nearly identical across tools.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pitfalls and guardrails (brief)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lockfile version mismatch&lt;/strong&gt;: Align lockfile versions before you start.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unexpected dependency changes&lt;/strong&gt;: Rely on the diff script to detect. Review and accept or fix intentionally.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Unifying multiple apps under a single root workspace and lockfile simplifies dependency management and installations across a large monorepo. A small amount of automation — optional seed-merge and a deterministic diff — makes the migration observable and CI-friendly without relying on bespoke, fragile lockfile reconciliation.&lt;/p&gt;

</description>
      <category>monorepo</category>
      <category>tooling</category>
      <category>javascript</category>
      <category>abotwrotethis</category>
    </item>
    <item>
      <title>The Hidden Costs of Switching from NetBeans to Cursor</title>
      <dc:creator>JLarky</dc:creator>
      <pubDate>Tue, 17 Jun 2025 20:39:00 +0000</pubDate>
      <link>https://dev.to/jlarky/the-hidden-costs-of-switching-from-netbeans-to-cursor-53jg</link>
      <guid>https://dev.to/jlarky/the-hidden-costs-of-switching-from-netbeans-to-cursor-53jg</guid>
      <description>&lt;p&gt;For developers raised on NetBeans, the transition to a modern, AI-first IDE like Cursor can feel like stepping from a familiar kitchen into a spaceship. Cursor is fast, sleek, and intelligent—but also minimalist and opinionated in ways that can clash with long-ingrained workflows.&lt;/p&gt;

&lt;p&gt;While the benefits are real—AI-powered autocomplete, a VS Code ecosystem, and blazing performance—getting there isn't frictionless. Here’s a breakdown of the challenges you might face when trading the old-school reliability of NetBeans for the cutting-edge world of Cursor.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Comfort vs. Performance: Muscle Memory Fights Back
&lt;/h2&gt;

&lt;p&gt;If you’ve used NetBeans for years, you’ve developed more than preferences—you’ve built instincts. Cursor strips much of that away.&lt;/p&gt;

&lt;p&gt;NetBeans offers rich GUI interfaces for almost everything: running tests, inspecting services, managing databases, and even designing UIs. Cursor assumes you're comfortable with the keyboard, command-line tooling, and AI-driven suggestions. That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You might search in vain for the familiar “New Class” wizard.&lt;/li&gt;
&lt;li&gt;There’s no Project Properties dialog—just &lt;code&gt;settings.json&lt;/code&gt; and extensions.&lt;/li&gt;
&lt;li&gt;Multi-tab panes like &lt;em&gt;Projects&lt;/em&gt;, &lt;em&gt;Files&lt;/em&gt;, and &lt;em&gt;Services&lt;/em&gt; are replaced with a single file explorer and fuzzy search.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even basic things—like setting breakpoints or viewing the call hierarchy—can feel like a scavenger hunt until you rebuild your muscle memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Project Types: Java-Centric IDE vs. Language-Agnostic Editor
&lt;/h2&gt;

&lt;p&gt;NetBeans is unapologetically Java-first. Cursor, built on top of VS Code, is language-agnostic with a heavy lean toward TypeScript and web tech.&lt;/p&gt;

&lt;p&gt;In NetBeans, opening a Maven project just works. Dependencies resolve, code completion is accurate, and run/debug configurations are pre-wired. Cursor, by contrast, might require manual setup, extension installation, or build tool configuration before things behave.&lt;/p&gt;

&lt;p&gt;You’ll especially feel the gap if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You rely on NetBeans' deep integrations with Tomcat, GlassFish, or Payara.&lt;/li&gt;
&lt;li&gt;You're working with Java EE or Jakarta EE.&lt;/li&gt;
&lt;li&gt;You use Ant or Maven-based desktop applications or GUI projects like JavaFX or Swing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cursor isn’t hostile to Java—it just assumes you’ll bring your own build setup and tools. And if you’re building something monolithic or backend-heavy in Java, the friction can be real.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. AI-First Isn’t Always Instant Productivity
&lt;/h2&gt;

&lt;p&gt;Cursor’s selling point is AI as a first-class IDE citizen. You can highlight a chunk of code and ask questions in natural language. It can autocomplete large blocks, summarize complex logic, or explain what a regex does.&lt;/p&gt;

&lt;p&gt;That sounds magical. But here’s the catch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need to know how to &lt;em&gt;ask&lt;/em&gt; good questions.&lt;/li&gt;
&lt;li&gt;If you’re not careful, you’ll end up with plausible-looking code that doesn’t quite work.&lt;/li&gt;
&lt;li&gt;Relying too much on AI suggestions without fully understanding them can create technical debt faster than you realize.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For NetBeans users used to precision and manual control, AI-generated assistance might feel too ambiguous or too eager. There’s power here—but it takes time to wield it effectively.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Debugging: From Integrated Simplicity to DIY Configs
&lt;/h2&gt;

&lt;p&gt;NetBeans’ Java debugger is battle-tested. Step-through debugging, conditional breakpoints, thread inspectors—it all works without fuss. Cursor takes a lighter approach.&lt;/p&gt;

&lt;p&gt;Debugging in Cursor means configuring &lt;code&gt;launch.json&lt;/code&gt;, installing the right Java extensions, and occasionally fiddling with your environment variables. Once set up, it works—but the ramp-up is nontrivial. Especially if you're used to clicking “Debug Project” and just watching your app go.&lt;/p&gt;

&lt;p&gt;Expect growing pains around:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remote debugging.&lt;/li&gt;
&lt;li&gt;Attaching to running processes.&lt;/li&gt;
&lt;li&gt;Inspecting heap or thread state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can get there—but you’ll spend time on StackOverflow and GitHub issues along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Build Systems &amp;amp; Tooling Expectations
&lt;/h2&gt;

&lt;p&gt;NetBeans likes to manage things for you. It handles your &lt;code&gt;pom.xml&lt;/code&gt;, runs your tests, and offers rich visual interfaces for everything from profiling to deployment.&lt;/p&gt;

&lt;p&gt;Cursor assumes the opposite: that you're running &lt;code&gt;mvn test&lt;/code&gt; in the terminal and linting via command-line tools or preconfigured tasks. It’s not worse—it’s just manual.&lt;/p&gt;

&lt;p&gt;This also means you’ll need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install your preferred formatter and linter manually.&lt;/li&gt;
&lt;li&gt;Set up test runners (JUnit, Jest, etc.) via extensions or scripts.&lt;/li&gt;
&lt;li&gt;Configure build outputs explicitly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This shift gives you more control, but it also exposes you to more moving parts.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Extension Overload: From All-In-One to Choose-Your-Own Adventure
&lt;/h2&gt;

&lt;p&gt;NetBeans is an IDE in the traditional sense: batteries included. Cursor inherits the VS Code approach—start small, add what you need. This is liberating and dangerous.&lt;/p&gt;

&lt;p&gt;You’ll be faced with decisions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which Java extension pack is best?&lt;/li&gt;
&lt;li&gt;Do I need the AI inline autocomplete &lt;em&gt;and&lt;/em&gt; the chat panel?&lt;/li&gt;
&lt;li&gt;Should I install a test runner plugin or wire up CLI scripts?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each choice adds power—but also fragility. Some extensions conflict. Some silently fail. And keeping your environment stable across machines or teams becomes another layer of complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bigger Picture
&lt;/h2&gt;

&lt;p&gt;The move from NetBeans to Cursor is more than just switching editors. It’s a philosophical shift—from IDE-managed workflows to lean, scriptable, AI-augmented development. From safe defaults to fast iteration. From vertical integration to composable tooling.&lt;/p&gt;

&lt;p&gt;Cursor can be faster. It can be smarter. But it doesn’t hold your hand—and it won’t protect you from misusing the power it gives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is It Worth It?
&lt;/h2&gt;

&lt;p&gt;If you're building Java apps for enterprise deployment, NetBeans might still serve you better. But if you're living in a polyglot world, hopping between Python, Node, TypeScript, and Go—and if you're ready to leverage AI as a coding assistant—Cursor can open new doors.&lt;/p&gt;

&lt;p&gt;Just don’t expect a painless transition. Expect friction. Expect to feel slow before you feel fast again.&lt;/p&gt;

&lt;p&gt;And once you’ve adjusted… you might never want to go back.&lt;/p&gt;

</description>
      <category>abotwrotethis</category>
    </item>
    <item>
      <title>What Most People Get Wrong About the Term SSR</title>
      <dc:creator>JLarky</dc:creator>
      <pubDate>Wed, 27 Nov 2024 18:56:57 +0000</pubDate>
      <link>https://dev.to/jlarky/what-most-people-get-wrong-about-the-term-ssr-3ijo</link>
      <guid>https://dev.to/jlarky/what-most-people-get-wrong-about-the-term-ssr-3ijo</guid>
      <description>&lt;p&gt;The term &lt;strong&gt;Server-Side Rendering (SSR)&lt;/strong&gt; is often misunderstood, with many using it to describe practices that predate its creation or don’t technically qualify. From PHP templates to React’s isomorphic apps, the definition of SSR has evolved—and so has the confusion around it.&lt;/p&gt;

&lt;p&gt;This article dives into the origins of SSR, what it truly means, and why understanding the distinction matters in modern web development.&lt;/p&gt;

&lt;h2&gt;
  
  
  So Here’s the Deal
&lt;/h2&gt;

&lt;p&gt;We didn’t have SSR back in the PHP days. That term didn’t exist. It was created in the 2010s. No one called this stuff SSR before that.  &lt;/p&gt;

&lt;p&gt;What did they call it? If you believe Wikipedia, it was called &lt;strong&gt;server-side scripting&lt;/strong&gt; (as opposed to client-side scripting).  &lt;/p&gt;

&lt;p&gt;Fun fact: if you check Wikipedia, they didn’t even add “SSR” to the &lt;strong&gt;server-side scripting&lt;/strong&gt; article until 2021. &lt;a href="https://en.wikipedia.org/w/index.php?title=Server-side_scripting&amp;amp;diff=prev&amp;amp;oldid=1049194504" rel="noopener noreferrer"&gt;Here’s the diff&lt;/a&gt;. And honestly? I think this is wrong.  &lt;/p&gt;




&lt;h2&gt;
  
  
  Before SSR, There Was...
&lt;/h2&gt;

&lt;p&gt;Until React introduced the term “rendering,” we didn’t use that word. The closest thing we had was &lt;strong&gt;server-side templates&lt;/strong&gt;. &lt;a href="https://en.wikipedia.org/w/index.php?title=Web_template_system&amp;amp;oldid=90484844" rel="noopener noreferrer"&gt;Here’s an old snapshot&lt;/a&gt;.  &lt;/p&gt;

&lt;p&gt;The idea was simple: you’d use a &lt;strong&gt;static site generator&lt;/strong&gt; or &lt;strong&gt;server scripting&lt;/strong&gt; to build your dynamic web page.  &lt;/p&gt;

&lt;p&gt;Some people argue: “Well, if I use server templates, I’m rendering them on the server.”  &lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem With That
&lt;/h2&gt;

&lt;p&gt;Rendering in React doesn’t always mean producing HTML or DOM. It produces &lt;strong&gt;VDOM&lt;/strong&gt; (virtual DOM). The lines blur when you call &lt;code&gt;renderToString&lt;/code&gt; because then the component is actually rendered to HTML.  &lt;/p&gt;

&lt;p&gt;This is why people started claiming their PHP apps were doing SSR. But here’s the issue: this loses the distinction between actual SSR and regular dynamic scripting.  &lt;/p&gt;




&lt;h2&gt;
  
  
  The Main Difference
&lt;/h2&gt;

&lt;p&gt;You can only do SSR on parts that could also be rendered on the client.  &lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleClick&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Hello&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can run this app twice: once on the server and once on the client.  &lt;/p&gt;

&lt;p&gt;But:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Hello"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can’t run on the client. There’s no rendering here—no “client-side” or “server-side” distinction. This is just old-fashioned dynamic scripting.  &lt;/p&gt;




&lt;h2&gt;
  
  
  SR vs. SSR
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3rzq71h9qufsua0l5lir.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3rzq71h9qufsua0l5lir.png" alt="Image description" width="800" height="146"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since no one uses those old terms anymore (except maybe in ASP), I think I’m giving up and just calling it &lt;strong&gt;Server Rendering (SR)&lt;/strong&gt; vs. &lt;strong&gt;Server-Side Rendering (SSR)&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;One huge difference is &lt;strong&gt;hydration&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;In the PHP world, there’s no hydration, but they’re still sure they have SSR. That doesn’t make sense. You can only have SSR if you have hydration.  &lt;/p&gt;




&lt;h2&gt;
  
  
  Hydration: The Key
&lt;/h2&gt;

&lt;p&gt;React has two key methods:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;renderToStaticMarkup&lt;/code&gt;&lt;/strong&gt;: Produces HTML you’re not expected to hydrate. This is closer to server templating.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;renderToString&lt;/code&gt;&lt;/strong&gt;: Produces HTML that gets hydrated on the client. This is SSR.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Angular Universal didn’t have SSR until 2023. What they had was SR: producing HTML on the server, then dropping it once scripts loaded and rendering the app as an SPA into an empty &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; tag.  &lt;/p&gt;

&lt;p&gt;That’s not the same as PHP, but it’s also not the same as real SSR.  &lt;/p&gt;




&lt;h2&gt;
  
  
  The Early Days
&lt;/h2&gt;

&lt;p&gt;Early on, React apps were “pre-rendered” using headless Chrome to save them as HTML strings. That snapshot went into a CDN. Technically, a server wasn’t even necessary to make this work. 😂  &lt;/p&gt;

&lt;p&gt;It was a pointless endeavor, but Google recommended it for SEO at one point. I tracked down that article once, but I’m not sure if I can find it again.  &lt;/p&gt;




&lt;h2&gt;
  
  
  Why Care About This?
&lt;/h2&gt;

&lt;p&gt;React Server Components (RSC) forced us to revisit this topic.  &lt;/p&gt;

&lt;p&gt;Technically, RSC doesn’t do SSR. This surprised a lot of people.  &lt;/p&gt;

&lt;p&gt;The React team tried explaining it but gave up. The gist is that server components are just templates—they produce static HTML. Client components go through SSR to produce both HTML and DOM.  &lt;/p&gt;




&lt;h2&gt;
  
  
  Inertia.js and SSR
&lt;/h2&gt;

&lt;p&gt;Inertia.js makes a similar distinction. PHP runs on the server, but your JavaScript app gets SSR’d by running on the server to produce HTML and then hydrating on the client.  &lt;/p&gt;




&lt;h2&gt;
  
  
  So, Can PHP Do SSR?
&lt;/h2&gt;

&lt;p&gt;No. Like RSC, PHP is doing &lt;strong&gt;dynamic scripting (SR)&lt;/strong&gt; with a step that does SSR.  &lt;/p&gt;

&lt;p&gt;If you run a React app with a middleware like Hono, injecting some dynamic code into HTML and later calling &lt;code&gt;renderToString&lt;/code&gt;, it feels similar. In both cases, it’s SR with a step of SSR.  &lt;/p&gt;

&lt;p&gt;That’s why it’s bonkers when people claim, “We did SSR in PHP in the ’90s.”  &lt;/p&gt;




&lt;h2&gt;
  
  
  What About SSG?
&lt;/h2&gt;

&lt;p&gt;Every time I bring this up, someone asks about SSG. I don’t care.  &lt;/p&gt;

&lt;p&gt;The term &lt;strong&gt;Static Site Generation (SSG)&lt;/strong&gt; actually predated React. SSG means producing HTML—no rendering or hydration required. Did you produce HTML? Congrats, you’re doing SSG.  &lt;/p&gt;




&lt;h2&gt;
  
  
  The React Innovation
&lt;/h2&gt;

&lt;p&gt;React frameworks introduced &lt;strong&gt;isomorphic apps&lt;/strong&gt;, using hydration to adopt HTML on the client without re-creating it.  &lt;/p&gt;

&lt;p&gt;That HTML had to be produced by SSR.  &lt;/p&gt;




&lt;h2&gt;
  
  
  Qwik and “Resumability”
&lt;/h2&gt;

&lt;p&gt;Does Qwik do hydration? That’s the big question.  &lt;/p&gt;

&lt;p&gt;Qwik developers say no, but I’m leaning towards yes. If you like Qwik, you’d need to chop off another piece of SSR and call it &lt;strong&gt;Resumability&lt;/strong&gt;.  &lt;/p&gt;




&lt;p&gt;If you prefer listening to discussions over reading, you can hear more of these arguments in audio form from this podcast episode about &lt;a href="https://www.youtube.com/watch?v=7Co0qXGcE5I&amp;amp;t=499s" rel="noopener noreferrer"&gt;React Server Components in Go&lt;/a&gt;&lt;/p&gt;

</description>
      <category>abotwrotethis</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Argument Against Solving the Double Data Problem in JavaScript SSR Frameworks</title>
      <dc:creator>JLarky</dc:creator>
      <pubDate>Sun, 03 Nov 2024 22:19:50 +0000</pubDate>
      <link>https://dev.to/jlarky/argument-against-solving-the-double-data-problem-in-javascript-ssr-frameworks-5dpj</link>
      <guid>https://dev.to/jlarky/argument-against-solving-the-double-data-problem-in-javascript-ssr-frameworks-5dpj</guid>
      <description>&lt;p&gt;The "double data problem" in JavaScript Server-Side Rendering (SSR) frameworks refers to the redundancy of sending the same data twice—once in the HTML output generated by the server and again as serialized data to enable client-side hydration. While addressing this issue may seem beneficial, there are compelling arguments against solving it due to trade-offs in complexity, real-world performance, and developer experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Complexity and Fragility
&lt;/h2&gt;

&lt;p&gt;Attempting to resolve the double data problem introduces additional complexity in the codebase, as frameworks would need intricate optimizations to avoid sending data twice. This added complexity can make frameworks more fragile and harder to debug, potentially increasing maintenance costs and slowing down development. Solutions to this problem could add more points of failure, making the SSR framework less reliable and harder to work with.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Limited Real-World Performance Gains
&lt;/h2&gt;

&lt;p&gt;For many applications, the data being duplicated is often small in size, especially compared to other assets like images, CSS, and JavaScript bundles. In these cases, the actual performance gain from reducing double data transmission is likely marginal, yielding negligible improvements in page load times. When network speed or payload size is not a bottleneck, optimizing SSR hydration to solve the double data problem may not deliver noticeable benefits to the end user.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Prioritizing Development Time and Impactful Optimizations
&lt;/h2&gt;

&lt;p&gt;Developers generally need to prioritize optimizations that deliver the most significant impact on user experience. Optimizing for the double data problem may not be the best use of development time, especially when there are other optimizations (like selective hydration or bundling) that could yield greater improvements in user experience. With limited development resources, it may be more effective to focus on optimizations that meaningfully enhance load times and interactivity.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Loss of Flexibility and Developer Experience
&lt;/h2&gt;

&lt;p&gt;Existing SSR frameworks that have the double data problem allow for a straightforward approach to data usage, where data can seamlessly be accessed on both the server and client side. Trying to eliminate this redundancy could complicate data handling, requiring developers to track data states more closely and rethink data-fetching patterns. This could make frameworks harder to learn and potentially less intuitive to use, impacting developer productivity and flexibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Existing Strategies Alleviate Performance Concerns
&lt;/h2&gt;

&lt;p&gt;Many frameworks are already exploring alternative hydration strategies, such as selective hydration, which optimizes performance without addressing the double data problem directly. These strategies allow only essential components to hydrate initially, reducing data transmission costs and improving load times without requiring a full solution to the double data problem. Additionally, techniques like Gzip/Brotli compression and caching minimize the impact of sending data twice by compressing the HTML and JSON payloads, making it more manageable and often negligible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;While the double data problem is an inefficiency, addressing it may not yield substantial real-world benefits for most applications. Solving this issue could lead to increased code complexity, reduced developer flexibility, and only minor performance gains. By focusing on alternative optimizations, such as selective hydration and compression, frameworks can enhance performance effectively without the drawbacks associated with solving the double data problem. Thus, in most cases, it may be more pragmatic to accept this inefficiency rather than introduce new complexities into SSR frameworks.&lt;/p&gt;

</description>
      <category>abotwrotethis</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Conway's Law and Separation of Concerns in Web Development</title>
      <dc:creator>JLarky</dc:creator>
      <pubDate>Mon, 21 Oct 2024 13:59:54 +0000</pubDate>
      <link>https://dev.to/jlarky/conways-law-and-separation-of-concerns-in-web-development-4cfk</link>
      <guid>https://dev.to/jlarky/conways-law-and-separation-of-concerns-in-web-development-4cfk</guid>
      <description>&lt;p&gt;Conway's Law, which states that software systems tend to mirror the communication structures of the organizations that build them, plays a crucial role in the way modern web development is structured. The evolution from early practices to today’s more complex systems, like micro-frontends and component-based architectures, has been largely shaped by this principle. By looking at how concerns were historically separated in web development, we can better understand how current practices emerged and why they look the way they do today.&lt;/p&gt;

&lt;p&gt;In the early days of web development, different teams were often responsible for specific technologies. One team handled the HTML, another was in charge of CSS, and yet another team took care of JavaScript and server-side logic, such as PHP. This clear separation of responsibilities, or "separation of concerns," was driven by the distinct skills each team possessed. Designers would hand over pixel-perfect Photoshop files to one team, who would then turn those into HTML and CSS templates. Once the templates were done, the next team would integrate them into the app, often encountering friction when things didn’t fit perfectly.&lt;/p&gt;

&lt;p&gt;A designer might deliver a .psd file with all nine corners of a table meticulously designed, and the HTML/CSS team would slice it into a working layout. But they were largely disconnected from the app's actual logic or user interactions. Their job was just to make sure the visuals worked. The backend team, dealing with PHP and JavaScript, would then integrate these static templates into the functioning app, often finding the solutions presented by the earlier teams weren’t ideal for the application’s needs. This was a reflection of how organizations were structured, with each team owning a different part of the process without much cross-communication.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Shift to Component-based Architecture
&lt;/h3&gt;

&lt;p&gt;Today, the way we separate concerns has changed dramatically. Instead of splitting responsibilities by technology—such as one team for HTML and CSS, and another for JavaScript and PHP—modern teams are more likely to be responsible for the entire stack of specific parts of the application. Each team typically owns a vertical slice of the application, including everything from frontend components to backend logic. This shift is driven by the rise of component-based architectures, where reusable, self-contained components are the building blocks of the system.&lt;/p&gt;

&lt;p&gt;For example, instead of one team focusing on all the HTML and CSS across the entire site, and another team handling the JavaScript and server-side integration, you now have teams that are responsible for distinct features or components, such as &lt;code&gt;&amp;lt;Article&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;ChatWidget&amp;gt;&lt;/code&gt;, or &lt;code&gt;&amp;lt;MainLayout&amp;gt;&lt;/code&gt;. Each team manages their component or part of the application from top to bottom, including both the frontend and backend logic. This allows teams to work more autonomously, reducing bottlenecks and miscommunication that often occurred in the old separation model.&lt;/p&gt;

&lt;p&gt;This new separation of concerns, by feature or component rather than by technology, allows teams to iterate faster. A team responsible for a chat widget, for example, can implement changes to both the UI and the backend API without waiting for another team to handle one part of the system. The key difference now is that instead of having specialized teams focused only on HTML or JavaScript, you have cross-functional teams that take ownership of their components or features in their entirety.&lt;/p&gt;

&lt;h3&gt;
  
  
  Micro-frontends and Independent Team Ownership
&lt;/h3&gt;

&lt;p&gt;One of the most significant results of this shift is the rise of micro-frontends, where different teams own different parts of the frontend, just as they own parts of the backend. This allows for a level of independence that wasn’t possible in the early days. A micro-frontend architecture mirrors the independence teams now have in managing their components.&lt;/p&gt;

&lt;p&gt;For example, a team responsible for &lt;code&gt;&amp;lt;MainLayout&amp;gt;&lt;/code&gt; might own everything from the UI structure to how it interacts with the data fetched from APIs. Another team responsible for &lt;code&gt;&amp;lt;Article&amp;gt;&lt;/code&gt; will have full control over how articles are fetched, rendered, and interacted with, from frontend logic to database queries. This level of autonomy means that changes can be deployed independently, without needing to coordinate with other teams as much as in the past.&lt;/p&gt;

&lt;p&gt;In contrast, in the old HTML+CSS vs. JS+PHP separation model, changes to any part of the system required coordination between multiple teams. If the frontend needed a new feature, the HTML/CSS team would have to work with the JavaScript team to ensure the new layout or functionality worked as intended. Today, with teams owning specific components or features from top to bottom, this need for inter-team coordination is greatly reduced, allowing for more rapid development and deployment cycles.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conway's Law in Action
&lt;/h3&gt;

&lt;p&gt;Conway’s Law remains as relevant as ever. The way we build software today still reflects the way our teams are organized, but the difference is that modern team structures are more feature-focused and less technology-siloed. The old method of splitting responsibilities by technology (HTML+CSS vs. JS+PHP) has given way to a model where each team is responsible for a complete feature or component.&lt;/p&gt;

&lt;p&gt;This modern separation of concerns allows for better communication within teams and more focused ownership. Micro-frontends, component-based architectures, and feature-focused teams are all reflections of Conway’s insight: that your software will inevitably reflect the structure of your team. As our team structures evolve, so too do the systems we build, becoming more flexible, modular, and independent.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;The shift from technology-based separation of concerns to feature-based separation has revolutionized how we build web applications. Conway's Law explains why this evolution happened: as teams have become more autonomous and feature-focused, the architecture of our systems has followed suit. Micro-frontends, internal component libraries, and component-based development all reflect the modern need for independent, cross-functional teams that own both the frontend and backend of their specific features or components.&lt;/p&gt;

&lt;p&gt;While the tools and frameworks have evolved, the fundamental principle remains the same: the way teams are structured directly influences the software they build. By understanding Conway’s Law and the history of separation of concerns, we can better appreciate the systems we work with today and anticipate how they might continue to evolve.&lt;/p&gt;

</description>
      <category>abotwrotethis</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Creating an NPM Package in 2024 (Deno, dnt)</title>
      <dc:creator>JLarky</dc:creator>
      <pubDate>Sat, 23 Sep 2023 03:57:18 +0000</pubDate>
      <link>https://dev.to/jlarky/creating-an-npm-package-in-2024-deno-dnt-3467</link>
      <guid>https://dev.to/jlarky/creating-an-npm-package-in-2024-deno-dnt-3467</guid>
      <description>&lt;h2&gt;
  
  
  Jokey Intro
&lt;/h2&gt;

&lt;p&gt;So, you want to create an npm package? My advice? Don't. It's a lot of work, and the results are often not worth the effort. At the time of me writing this, npm has 2,532,666 packages. By the time you finish this article, it's guaranteed there will be at least 10 more.&lt;/p&gt;

&lt;p&gt;On the surface, it sounds simple: write some code in a file and push it to &lt;a href="https://www.npmjs.com/"&gt;npm&lt;/a&gt;. But you can't just push the code; you also need a &lt;code&gt;package.json&lt;/code&gt; filled with all sorts of metadata. You can't merely push the source code either. If you're using TypeScript (and you should be), suddenly you have a build step, bundlers, minifiers, CommonJS, ES Modules — the list goes on.&lt;/p&gt;

&lt;h2&gt;
  
  
  I ain't reading all that. I'm happy for u tho. Or sorry that happened.
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/JLarky/is-not-bun"&gt;GH repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/live/A19Jvot9hI4?si=Rf5iakJOPmcTEMxo"&gt;Youtube tutorial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/is-not-bun"&gt;package on npm&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Best Way to Author an NPM Package is Deno
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://deno.com/"&gt;Deno&lt;/a&gt; is an alternative JavaScript runtime that comes with built-in lint, TypeScript, and bundling capabilities. You don't need to be familiar with Deno to follow this tutorial.&lt;/p&gt;

&lt;p&gt;For the first step, &lt;a href="https://docs.deno.com/runtime/manual/getting_started/installation"&gt;install Deno&lt;/a&gt;. If you're on a Mac or Linux, it's just one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -fsSL https://deno.land/x/install/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  1. Create a New Project
&lt;/h2&gt;

&lt;p&gt;Create a new folder for your project and run &lt;code&gt;deno init&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir is-not-bun
cd is-not-bun
deno init
touch README.md LICENSE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;is-not-bun&lt;/code&gt; with the name of your package. This tutorial is the text version of this &lt;a href="https://www.youtube.com/live/A19Jvot9hI4?si=Rf5iakJOPmcTEMxo"&gt;Youtube tutorial&lt;/a&gt; and will address the same problem space of determining if a user is running an npm package using &lt;a href="https://bun.sh/"&gt;Bun&lt;/a&gt; (you don't need to know about Bun either).&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Create your code
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;mod.ts&lt;/code&gt; and write your code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isBun&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;npm:is-bun&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;isNotBun&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isBun&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;printIsNotBun&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isNotBun&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;No Bun, no problem&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Help, I'm trapped in a Bun factory&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create &lt;code&gt;cli.ts&lt;/code&gt; and write your CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;printIsNotBun&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./mod.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;printIsNotBun&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And run it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;deno run cli.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will see the output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;No Bun, no problem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the difference, because we are using Deno we have to add &lt;code&gt;npm:&lt;/code&gt; prefix when importing npm packages, but the positive is that we don't need to run &lt;code&gt;npm install&lt;/code&gt;, because Deno will automatically download the package from NPM.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. DNT build script
&lt;/h2&gt;

&lt;p&gt;We are going to use &lt;a href="https://github.com/denoland/dnt"&gt;dnt - Deno to Node Transform&lt;/a&gt; tool to build the npm package.&lt;/p&gt;

&lt;p&gt;I recommend you to checkout other examples of DNT configs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/oakserver/oak/blob/main/_build_npm.ts"&gt;oak&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/upstash/upstash-redis/blob/main/cmd/build.ts"&gt;upstash&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/semicognitive/sveltekit-modal/blob/ec38e1393cadcbba2a4f6dc09cb0a8da445a3f8f/dnt.ts#L4"&gt;sveltekit-modal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/MasterKale/SimpleWebAuthn/blob/master/packages/typescript-types/build_npm.ts"&gt;SimpleWebAuthn&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Create a file &lt;code&gt;_build_npm.ts&lt;/code&gt; and add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="cp"&gt;#!/usr/bin/env -S deno run --allow-read --allow-write --allow-net --allow-env --allow-run
&lt;/span&gt;&lt;span class="c1"&gt;// Copyright 2018-2022 the oak authors. All rights reserved. MIT license.&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * This is the build script for building npm package.
 *
 * @module
 */&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;emptyDir&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://deno.land/x/dnt@0.38.1/mod.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;emptyDir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./npm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;entryPoints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="c1"&gt;// change me&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./mod.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;is-not-bun&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./cli.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;outDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./npm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;shims&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;typeCheck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;both&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;compilerOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;importHelpers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;sourceMap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ES2021&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;lib&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;esnext&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dom&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dom.iterable&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;package&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;is-not-bun&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// change me&lt;/span&gt;
      &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Return true if you are running not in Bun.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// change me&lt;/span&gt;
      &lt;span class="na"&gt;license&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MIT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;keywords&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bun&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// change me&lt;/span&gt;
      &lt;span class="na"&gt;engines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;=8.0.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;git&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;git+https://github.com/JLarky/is-not-bun.git&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// change me&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;bugs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://github.com/JLarky/is-not-bun/issues&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// change me&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;is-bun&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// change me&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;devDependencies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;copyFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;LICENSE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;npm/LICENSE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;copyFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;README.md&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;npm/README.md&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure to replace &lt;code&gt;is-not-bun&lt;/code&gt; with the name of your project, change your description, keywords, repository, dependencies and entry points.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;"./mod.ts"&lt;/code&gt; is the name of the &lt;code&gt;"main"&lt;/code&gt; entry in your package, most of the time that's the only entry point you need. But if you are building a CLI tool (for example &lt;code&gt;tsc&lt;/code&gt;, &lt;code&gt;ts-node&lt;/code&gt;), that's when &lt;code&gt;kind: "bin",&lt;/code&gt; comes into play. If you are not planning to build a CLI tool, you can remove the second entry point (the whole object).&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Build your package
&lt;/h2&gt;

&lt;p&gt;First, let's add &lt;code&gt;npm&lt;/code&gt; to &lt;code&gt;.gitignore&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"npm"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; .gitignore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And make build script executable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x _build_npm.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can build the version &lt;code&gt;0.0.1&lt;/code&gt; of our package with the build script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./_build_npm.ts 0.0.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can check that the build was successful by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;npm/&lt;span class="p"&gt;;&lt;/span&gt; node &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"console.log(require('.'))"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The expected output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;isNotBun:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;Function:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;isNotBun&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;printIsNotBun:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;Function:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;printIsNotBun&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Publish your package
&lt;/h2&gt;

&lt;p&gt;We are at the final step, we can publish our package to NPM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;npm/&lt;span class="p"&gt;;&lt;/span&gt; npm publish&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep in mind that if you never published to npm before you will have to create an account and login first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  That's it
&lt;/h2&gt;

&lt;p&gt;Let's celebrate a bit what we have accomplished:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We were able to author our package in TypeScript and because we are using Deno we can run &lt;code&gt;deno run cli.ts&lt;/code&gt; without separate build step or even &lt;code&gt;npm install&lt;/code&gt; and get nice developer experience (even though we were using dependencies from NPM)&lt;/li&gt;
&lt;li&gt;Using dnt we were able to create nice and modern package setup with three output formats: CommonJS (&lt;code&gt;npm/script/mod.js&lt;/code&gt;), ES Modules (&lt;code&gt;npm/esm/mod.js&lt;/code&gt;) and TypeScript source (&lt;code&gt;npm/src/mod.ts&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;That npm package is checking all the boxes, it generated &lt;code&gt;.d.ts&lt;/code&gt; files for TypeScript users and even &lt;code&gt;.js.map&lt;/code&gt; with source maps for debugging&lt;/li&gt;
&lt;li&gt;As a bonus we even got CLI that you can run with &lt;code&gt;npx is-not-bun&lt;/code&gt; or as a script in your package.json &lt;code&gt;"scripts": { "fun": "is-not-bun" }&lt;/code&gt; that you can run with &lt;code&gt;npm run fun&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;And we can use some nice built-in Deno tools as extra bonus, which I will cover in the next section&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Extra bonus
&lt;/h2&gt;

&lt;p&gt;This part is not technically required, remember when I said that you don't have to actually care about Deno? Well, this is where the sales pitch comes in.&lt;/p&gt;

&lt;p&gt;First things first, we have a few files that were created during &lt;code&gt;deno init&lt;/code&gt;, I'm going to guide you through them.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;deno.json&lt;/code&gt; and &lt;code&gt;main.ts&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Remember how I said that DX for development of packages is great in Deno? It's actually even better than that. &lt;code&gt;deno run&lt;/code&gt; supports &lt;code&gt;--watch&lt;/code&gt; flag that will re-run your code every time you save a file, no need for &lt;code&gt;nodemon&lt;/code&gt; and similar tools.&lt;/p&gt;

&lt;p&gt;Similar to &lt;code&gt;package.json&lt;/code&gt; Deno has &lt;code&gt;deno.json&lt;/code&gt; and at the moment it only has one task defined there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;deno task dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will run &lt;code&gt;main.ts&lt;/code&gt; with &lt;code&gt;--watch&lt;/code&gt; flag, so you can keep your development code in that file.&lt;/p&gt;

&lt;p&gt;So in the &lt;a href="https://github.com/JLarky/is-bun"&gt;GH repo&lt;/a&gt; here's the content of &lt;code&gt;main.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isNotBun&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./mod.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Learn more at https://deno.land/manual/examples/module_metadata#concepts&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;In Deno isNotBun is&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isNotBun&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;main_test.ts&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Deno has built-in test runner, so you don't need to install &lt;code&gt;vitest&lt;/code&gt; or &lt;code&gt;jest&lt;/code&gt;. To run tests you just do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;deno &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In GH repo this file looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;assertEquals&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://deno.land/std@0.195.0/testing/asserts.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isNotBun&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./mod.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;bunTest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;assertEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isNotBun&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;main_bench.ts&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;It looks like this file is no longer there with the new version of &lt;code&gt;deno init&lt;/code&gt;, but I still have it for old time sake. To run simple benchmarks you can do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;deno bench
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In GH repo this file looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isNotBun&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./mod.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;Deno&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bench&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;benchIsNotBun&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;isNotBun&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Creating npm packages can be challenging. We've seen many tools in this domain, and I encourage you to try dnt. With dnt, I was finally able to pass the &lt;a href="https://publint.dev/"&gt;package.json linter&lt;/a&gt;, which is no small achievement (try &lt;code&gt;jotai&lt;/code&gt;, &lt;code&gt;redux&lt;/code&gt;, &lt;code&gt;vite&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;This tutorial hasn't covered many dnt features. For more information, please check out this &lt;a href="https://twitter.com/deno_land/status/1676264059585560578"&gt;tweet&lt;/a&gt; for further reading and some replies from happy users.&lt;/p&gt;

</description>
      <category>npm</category>
      <category>deno</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Revisiting Web History from web 1.0 to RSC and HTMX</title>
      <dc:creator>JLarky</dc:creator>
      <pubDate>Mon, 10 Jul 2023 05:36:01 +0000</pubDate>
      <link>https://dev.to/jlarky/revisiting-web-history-from-web-10-to-rsc-and-htmx-4b9o</link>
      <guid>https://dev.to/jlarky/revisiting-web-history-from-web-10-to-rsc-and-htmx-4b9o</guid>
      <description>&lt;p&gt;Recently, I have been following HTMX talks and realized that while discussing the history of the web, we tend to simplify the evolution timeline, often omitting critical aspects. Let's delve deeper into the web's evolution journey from the beginning up to where HTMX and other web technologies currently stand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding Web According to HTMX
&lt;/h2&gt;

&lt;p&gt;HTMX explains the history of the web in a simple sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Web 1.0 existed with just HTML and no JS.&lt;/li&gt;
&lt;li&gt;Web 2.0 came along with Single Page Applications (SPAs) and JSON communication for everything.&lt;/li&gt;
&lt;li&gt;HTMX marked a return to the form.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;However, as someone who has experienced the evolution of the web first hand, my recollection differs slightly.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Recollection of the Web Evolution
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Dawn of Web Apps
&lt;/h3&gt;

&lt;p&gt;In my view, turning websites into applications began before CSS and JS had their footprints in web development. Around 1996-1997, we saw the emergence of ActiveX and Java applets, just a few years after CSS and JS were born.&lt;/p&gt;

&lt;p&gt;In the 2000s, technologies such as Flash, Silverlight, and XMLHttpRequest came to the fore. It's interesting to note that it was called XMLHttpRequest and not JSON HTTP Request, suggesting it was primarily designed to exchange HTML or XML. All this happened during the web 1.0 era, highlighting the app vs. website divide even then.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Era of Web 2.0
&lt;/h3&gt;

&lt;p&gt;The drastic shift came around 2005, marking the start of the web 2.0 era. Platforms like Drupal, WordPress, and others were heavily used. They utilized JavaScript (JS) and embraced AJAX or AJAJ, but the focus remained on using what already worked, i.e., good old HTML from web 1.0. This era gave birth to jQuery, catering to developers who were hesitant to learn JS and preferred to stick to server-side languages.&lt;/p&gt;

&lt;p&gt;However, 2005 was also the year when ActiveX died out, and the 2007 iPhone famously didn't support Flash. This change led to a massive demand for creating web apps that relied solely on HTML, CSS, and JS.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Evolution of JavaScript
&lt;/h3&gt;

&lt;p&gt;Over time, the question that plagued developers was: "Can I re-create existing Flash apps using only HTML, CSS, and JS?" This trend was a step in the right direction, despite the growing pressure to utilize users' CPU and memory resources.&lt;/p&gt;

&lt;p&gt;However, browser capabilities hadn't caught up yet. HTML5, CSS3, and ES5 weren't available, and JS was still considered unreliable. It's interesting to note that browsers like IE8 and even Chrome 1 didn't support &lt;code&gt;JSON.parse&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The 2010s - A Turning Point
&lt;/h3&gt;

&lt;p&gt;The years 2010-2012 were pivotal as browsers finally started catching up with emerging web technologies. HTML5, CSS3, and ES5 (along with JSON.parse) became commonplace. The History API emerged, allowing URL modifications without reloading the page. &lt;/p&gt;

&lt;p&gt;Around this time, a host of new technologies and frameworks began to surface. For instance, Angular, Ember, Backbone, and Knockout were introduced. With the rise of these technologies, the term Single Page Application (SPA) gained popularity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Web Transformation in 2015
&lt;/h3&gt;

&lt;p&gt;Fast forward to 2015, traditional web frameworks such as Drupal, WordPress, Django, and Rails had their time in the limelight. However, as mobile apps gained popularity, web traffic started shifting, with 50% coming from mobile devices. In response, Google introduced progressive web apps (PWA), Accelerated Mobile Pages (AMP), and Lighthouse, while promising to index SPA-style web apps.&lt;/p&gt;

&lt;p&gt;This period also saw the rise of a new generation of frameworks like React, Gatsby, Next.js, and others. &lt;/p&gt;

&lt;h3&gt;
  
  
  The Era of Overprescribed JS, JSON, and API
&lt;/h3&gt;

&lt;p&gt;From 2015 onwards, we've seen a persistent focus on JS, JSON, and APIs. Around 2020, traditional server frameworks finally started to adapt to the "HTML is now for apps" world. This was when we saw the rise of Blazor, Phoenix LiveView, Laravel Livewire, and of course, HTMX!&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s Next?
&lt;/h2&gt;

&lt;p&gt;Reflecting on the history of the web, we can observe two parallel streams of evolution: traditional websites with server-side logic and just a sprinkling of JS, and the more complex side starting with applets and aiming to provide an app-like experience.&lt;/p&gt;

&lt;p&gt;What seems to be the ongoing battle is finding a way to maintain the simplicity of traditional MPAs while delivering a user experience akin to SPAs. In this regard, both HTMX and React Server Components are arguing their case to be the best possible emulation of the MPA.&lt;/p&gt;

&lt;p&gt;Regardless of how you perceive "traditional web," what's fascinating is that HTML continues to stand tall, even with the decline of &lt;code&gt;/cgi-bin/hello.cgi&lt;/code&gt; Perl scripts. The concept of delivering an app over HTML remains alive and kicking, even in the face of competition from native apps.&lt;/p&gt;

&lt;p&gt;In the end, it is a reminder that the evolution of the web is continuous, filled with turning points and paradigm shifts. But amidst all these changes, the essence of web development, which lies in creating rich and seamless user experiences, remains unchanged.&lt;/p&gt;

</description>
      <category>abotwrotethis</category>
    </item>
    <item>
      <title>Using Git Bisect to Find the Commit that Broke ESLint</title>
      <dc:creator>JLarky</dc:creator>
      <pubDate>Fri, 23 Jun 2023 22:15:47 +0000</pubDate>
      <link>https://dev.to/jlarky/using-git-bisect-to-debug-faulty-commits-2blg</link>
      <guid>https://dev.to/jlarky/using-git-bisect-to-debug-faulty-commits-2blg</guid>
      <description>&lt;p&gt;In the world of software development, bugs and errors are inevitable. Even with the most comprehensive testing suite, some bugs might manage to slip into your codebase. So, when something goes wrong, how do you find the culprit commit?&lt;/p&gt;

&lt;p&gt;Luckily, Git provides a powerful tool for this very purpose, named &lt;code&gt;git bisect&lt;/code&gt;. It uses a binary search algorithm to quickly and efficiently find the exact commit that introduced a bug.&lt;/p&gt;

&lt;p&gt;In this blog post, we will guide you through the process of using &lt;code&gt;git bisect&lt;/code&gt;, specifically with the &lt;code&gt;git bisect run&lt;/code&gt; command, to debug a failing &lt;code&gt;yarn lint&lt;/code&gt; command. &lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding Git Bisect
&lt;/h2&gt;

&lt;p&gt;Git bisect is a binary search tool that helps you discover which specific commit introduced an error. This works by moving between commits, effectively halving the search range each time, until it narrows down to the faulty commit. &lt;/p&gt;

&lt;p&gt;You mark the known good and bad commits, and git bisect will do the rest. It's an extremely useful tool for large repositories where manually checking each commit would be tedious or nearly impossible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preparing for Bisect
&lt;/h2&gt;

&lt;p&gt;Before we start, ensure your repository is in a clean state; any changes should be committed or stashed. This is important because &lt;code&gt;git bisect&lt;/code&gt; will be switching between different commits during its search.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting the Bisect Process
&lt;/h2&gt;

&lt;p&gt;The first step is to start the bisect process with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git bisect start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we need to inform Git about the state of our current commit. Since we know that our current commit is causing the &lt;code&gt;yarn lint&lt;/code&gt; command to fail, we can mark it as "bad":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git bisect bad
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we need to specify a commit where we know that everything was working as expected - a "good" commit. Let's assume that the &lt;code&gt;origin/main&lt;/code&gt; branch does not have the bug. We can mark it as good with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git bisect good origin/main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you do this, Git will automatically checkout a commit in the middle of the range between the good and bad commits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automating the Bisect Process
&lt;/h2&gt;

&lt;p&gt;Here's where the magic happens. Instead of manually checking if each commit is good or bad, we can automate the process with &lt;code&gt;git bisect run&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git bisect run yarn lint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, Git will keep bisecting without your intervention until it finds the first bad commit. It will run the &lt;code&gt;yarn lint&lt;/code&gt; command on each commit, and if the command succeeds (returns 0), the commit is considered good. If it fails (returns anything other than 0), the commit is marked as bad.&lt;/p&gt;

&lt;p&gt;Once the faulty commit has been identified, Git will present a message like &lt;code&gt;This is the first bad commit&lt;/code&gt; along with the commit details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up the Bisect Process
&lt;/h2&gt;

&lt;p&gt;After Git has found the first bad commit, we need to end the bisect process. We do this with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git bisect reset
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will take you back to the commit you were on when you started the bisecting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Using &lt;code&gt;git bisect run&lt;/code&gt; can significantly simplify the process of identifying a problematic commit, especially in larger codebases. With the help of an automated command like &lt;code&gt;yarn lint&lt;/code&gt;, it makes it easier than ever to isolate issues and keep your project running smoothly. Understanding and using such powerful tools is what separates good developers from great ones, and is a step forward in writing more reliable, robust software.&lt;/p&gt;

</description>
      <category>abotwrotethis</category>
    </item>
    <item>
      <title>The Tectonic Shift in React Ecosystem: Unearthing the Future with Next.js, Remix, Gatsby, Vite, QGP, and Astro</title>
      <dc:creator>JLarky</dc:creator>
      <pubDate>Mon, 20 Mar 2023 14:37:59 +0000</pubDate>
      <link>https://dev.to/jlarky/the-tectonic-shift-in-react-ecosystem-unearthing-the-future-with-nextjs-remix-gatsby-vite-qgp-and-astro-okl</link>
      <guid>https://dev.to/jlarky/the-tectonic-shift-in-react-ecosystem-unearthing-the-future-with-nextjs-remix-gatsby-vite-qgp-and-astro-okl</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;The React ecosystem has recently witnessed a seismic shift, with the team's decision to move from recommending Create React App (CRA) to more feature-rich frameworks like Next.js, Remix, and Gatsby. Additionally, the rise of alternative tools like Vite, QGP, and Astro has stirred numerous discussions within the web development community, as developers ponder the implications for beginners and teams managing large projects. In this blog article, we'll briefly touch on the key points discussed during our live stream and invite you to watch the full video for a more in-depth exploration of this tectonic shift in the React landscape.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzc636cmf7o7k9py0lahh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzc636cmf7o7k9py0lahh.png" alt="CRA IS DEAD"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  React's Shift: apps written "fully in React"
&lt;/h2&gt;

&lt;p&gt;React's decision to switch from CRA to frameworks like Next.js, Remix, and Gatsby stems from the growing demand for more robust solutions in building production-grade applications. Alongside these frameworks, Vite, QGP, and Astro have emerged as popular alternatives, each bringing unique features and benefits to the table.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Next.js: A popular choice among developers, Next.js boasts features such as server-side rendering, static site generation, and API routes. Backed by Vercel, this framework enjoys a strong community presence and offers an opinionated approach to building React applications.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Remix: Developed by the creators of React Router, Remix focuses on server rendering and optimized loading of data and assets, providing a modern, fast, and enjoyable developer experience. Its flexible approach allows developers to customize their setup and choose the features they need.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Gatsby: Gatsby is a powerful static site generator that leverages GraphQL to manage data and build optimized websites. With its vast plugin ecosystem and focus on performance, Gatsby is an excellent option for content-driven websites and projects that require seamless data integration.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Vite: Created by Evan You, the creator of Vue.js, Vite is known for its simplicity and speed, making it an ideal solution for beginners looking for a more accessible entry point into React development without the added complexity of more advanced frameworks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;QGP: This open-source project aims to provide a smooth migration path from CRA to other solutions like Astro or Vite. It allows developers to run their applications in both environments simultaneously, making it easier to transition from one to the other gradually.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Astro: Astro is a new static site generator that allows developers to build fast, optimized websites using components from various frontend libraries, including React. With its user-friendly experience and focus on performance, Astro is an exciting addition to the React ecosystem.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Implications for Beginners
&lt;/h2&gt;

&lt;p&gt;The shift from CRA to more advanced frameworks has raised concerns about the impact on beginners. With additional complexity and features, newcomers may find frameworks like Next.js, Remix, and Gatsby overwhelming. However, alternatives like Vite and Astro offer more approachable options for those new to React. It's crucial to strike a balance between providing accessible options for beginners and offering powerful tools for experienced developers and large projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Teams Managing Large Projects
&lt;/h2&gt;

&lt;p&gt;For teams handling large-scale projects, the shift towards more feature-rich frameworks like Next.js, Remix, and Gatsby, as well as alternatives like Vite and Astro, presents exciting opportunities. These tools offer built-in performance optimizations, more advanced data handling, and better support for server-side rendering and static site generation. While the transition may require some effort, the long-term benefits could significantly improve the development process and the quality of the applications being built.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The ongoing evolution of the React ecosystem and the shift away from Create React App have brought new tools and frameworks to the forefront. As developers, it's essential to stay informed, adapt to these changes, and choose the best tools for our projects.&lt;/p&gt;

&lt;p&gt;We hope this blog article has piqued your interest in the live stream discussion, where we dive deeper into the implications of React's shift towards Next.js, Remix, and Gatsby, and explore the vibrant landscape of modern web development. So, don't miss out! Click the link below to watch the full video and join the conversation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/live/zDG6F7VXfxE" rel="noopener noreferrer"&gt;https://www.youtube.com/live/zDG6F7VXfxE&lt;/a&gt;&lt;/p&gt;

</description>
      <category>abotwrotethis</category>
    </item>
    <item>
      <title>Switching from React to Svelte: Why it's Time to Make the Move</title>
      <dc:creator>JLarky</dc:creator>
      <pubDate>Sun, 05 Mar 2023 05:11:22 +0000</pubDate>
      <link>https://dev.to/jlarky/switching-from-react-to-svelte-why-its-time-to-make-the-move-1b58</link>
      <guid>https://dev.to/jlarky/switching-from-react-to-svelte-why-its-time-to-make-the-move-1b58</guid>
      <description>&lt;p&gt;Good morning, fellow developers! Today, I want to talk to you about an important topic that's been on my mind for a while now: why it's time to switch from React to Svelte.&lt;/p&gt;

&lt;p&gt;Now, I know that many of you are probably comfortable with React. After all, it's been a popular choice for front-end development for years. But hear me out. Svelte is a newer, more modern approach to web development that offers some key advantages over React.&lt;/p&gt;

&lt;p&gt;Firstly, Svelte is much faster than React. Svelte compiles your code into highly optimized vanilla JavaScript, resulting in much smaller bundle sizes and faster load times. This means that your applications will be snappier and more responsive, providing a better user experience for your users.&lt;/p&gt;

&lt;p&gt;Secondly, Svelte is much easier to learn and use than React. With React, you need to learn a whole ecosystem of tools and libraries just to get started, which can be overwhelming for beginners. Svelte, on the other hand, has a much simpler and more intuitive syntax, making it easier to get up and running quickly.&lt;/p&gt;

&lt;p&gt;But it's not just about ease of use and performance. Svelte also offers some unique features that are not available in React. For example, Svelte's reactive programming model allows you to write code that is both more concise and more expressive than in React. Svelte also offers powerful animations and transitions out of the box, which can make your applications feel more polished and professional.&lt;/p&gt;

&lt;p&gt;Now, I know that switching from React to Svelte can seem like a daunting task. But trust me, it's worth it. With Svelte, you'll be able to build faster, more responsive applications with less code and less hassle. And as an added bonus, you'll be learning a cutting-edge technology that is quickly gaining popularity in the web development community.&lt;/p&gt;

&lt;p&gt;So let's take the plunge and switch from React to Svelte. Your users (and your sanity) will thank you for it. Happy coding!&lt;/p&gt;

</description>
      <category>abotwrotethis</category>
    </item>
  </channel>
</rss>
