<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="rss.xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Smorsic Labs Blog</title>
        <link>https://smorsic.io/blog/</link>
        <description>Smorsic Labs Blog</description>
        <lastBuildDate>Sun, 14 Jun 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <item>
            <title><![CDATA[pacwich: The Official Launch]]></title>
            <link>https://smorsic.io/blog/pacwich-launch</link>
            <guid>https://smorsic.io/blog/pacwich-launch</guid>
            <pubDate>Sun, 14 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[I'm the developer of bun-workspaces, a package for monorepo tooling that works with Bun's workspaces.]]></description>
            <content:encoded><![CDATA[<p>I'm the developer of <code>bun-workspaces</code>, a package for monorepo tooling that works with Bun's workspaces.</p>
<p>In deciding to support <code>npm</code> and <code>pnpm</code> as package managers as well, <code>bun-workspaces</code>'s name became obviously
inappropriate if I were to continue development.</p>
<p>With that, I'm introducing <a href="https://pacwich.dev/" target="_blank" rel="noopener noreferrer" class=""><code>pacwich</code></a>! This is the official continuation
of <code>bun-workspaces</code> with multi-package-manager support.</p>
<p><code>pacwich</code> is built from <code>bun-workspaces</code>'s core and is mostly backwards compatible.
If you're an existing user, you can use the <a href="https://pacwich.dev/intro/bun-workspaces-migration" target="_blank" rel="noopener noreferrer" class="">official migration guide</a>
and explore the docs, which have <a href="https://pacwich.dev/ai" target="_blank" rel="noopener noreferrer" class="">enhanced LLM integrations</a> as well.</p>
<p>Read on to learn more about my motivations and strategy for the pivot.</p>
<div style="display:flex;justify-content:center"><a href="https://pacwich.dev/" target="_blank"><img src="https://smorsic.io/blog/images/bwunster-pacwich-subtitled.png" width="300px" margin="auto"></a></div>
<pre class="syntax-highlighter" style="display:block;overflow-x:auto;padding:0.75rem 1rem;color:var(--code-base-color);background:#1c1b1b;border-radius:0.5rem;background-color:var(--code-block-bg);border:1px solid rgba(40, 44, 52, 0.52);line-height:1.4;margin:0.5rem 0"><code class="language-bash" style="white-space:pre"><span style="color:var(--code-comment-color)"># Global install with your preferred package manager</span><span>
</span><span></span><span style="color:var(--code-comment-color)"># The pacwich command will use a local install if available</span><span>
</span>bun add -g pacwich
<!-- -->pnpm add -g pacwich
<!-- -->npm install -g pacwich
<!-- -->
<span></span><span style="color:var(--code-comment-color)"># Local install in your project</span><span>
</span>bun add -d pacwich
<!-- -->pnpm add -D pacwich
<!-- -->npm install -D pacwich</code></pre>
<!-- -->
<p>The CLI is essentially backwards compatible, save for the fact that
the system shell (<code>sh</code>/<code>cmd.exe</code>) is now the default for inline scripts over the <a href="https://bun.sh/docs/runtime/shell" target="_blank" rel="noopener noreferrer" class="">Bun shell</a>,
since Bun is no longer guaranteed to be available due to Node and <code>npm</code>/<code>pnpm</code> support.</p>
<pre class="syntax-highlighter" style="display:block;overflow-x:auto;padding:0.75rem 1rem;color:var(--code-base-color);background:#1c1b1b;border-radius:0.5rem;background-color:var(--code-block-bg);border:1px solid rgba(40, 44, 52, 0.52);line-height:1.4;margin:0.5rem 0"><code class="language-bash" style="white-space:pre"><span style="color:var(--code-comment-color)"># Before (bun-workspaces):</span><span>
</span><span></span><span style="color:var(--code-built-in-color)">alias</span><span> bw=</span><span style="color:var(--code-string-color)">"bunx bun-workspaces"</span><span>
</span>bw ls
<span>bw run lint my-workspace </span><span style="color:var(--code-string-color)">"path:my-packages/**/*"</span><span> --parallel=2
</span><span></span><span style="color:var(--code-comment-color)"># Bun shell default for inline scripts:</span><span>
</span><span>bw run --inline </span><span style="color:var(--code-string-color)">"echo &lt;workspaceName&gt;"</span><span> --shell=bun
</span>
<span></span><span style="color:var(--code-comment-color)"># After (pacwich):</span><span>
</span><span></span><span style="color:var(--code-comment-color)"># You could alias "pw", but demonstrating the vanilla global install</span><span>
</span>pacwich ls
<span>pacwich run lint my-workspace </span><span style="color:var(--code-string-color)">"path:my-packages/**/*"</span><span> --parallel=2
</span>
<span></span><span style="color:var(--code-comment-color)"># "system" now the default shell for inline scripts</span><span>
</span><span></span><span style="color:var(--code-comment-color)"># Pass --shell=bun to use Bun shell instead if Bun is available:</span><span>
</span><span>pacwich run --inline </span><span style="color:var(--code-string-color)">"echo &lt;workspaceName&gt;"</span><span> --shell=bun</span></code></pre>
<h2 class="anchor anchorTargetStickyNavbar_HeLj" id="some-history">Some History<a href="https://smorsic.io/blog/pacwich-launch#some-history" class="hash-link" aria-label="Direct link to Some History" title="Direct link to Some History" translate="no">​</a></h2>
<p>Since early in my career, I have found monorepos to be very satisfying to work with.</p>
<p><code>npm</code>'s workspaces feature was the first tooling I used for monorepos, but it felt very bare-bones.</p>
<p>For many years, I had an itch I wanted to scratch by making my own monorepo tooling that
worked as directly with native tooling like as possible. I wanted it to be both lightweight and my own.</p>
<p>At some point after Bun's release, I started using it more and more until it became my package manager
of choice for TypeScript projects.</p>
<p>I created <code>bun-workspaces</code> on a whim to get around a now irrelevant limitation of Bun's <code>--filter</code>
feature. It gave me an excuse to start building the monorepo toolset I had wanted for years,
and I noticed that I was getting more positive reactions from other devs than I expected as a no-name,
but I didn't take the project very seriously until about a year later.</p>
<h2 class="anchor anchorTargetStickyNavbar_HeLj" id="why-change">Why Change?<a href="https://smorsic.io/blog/pacwich-launch#why-change" class="hash-link" aria-label="Direct link to Why Change?" title="Direct link to Why Change?" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="a-revelation-of-abstraction">A Revelation of Abstraction<a href="https://smorsic.io/blog/pacwich-launch#a-revelation-of-abstraction" class="hash-link" aria-label="Direct link to A Revelation of Abstraction" title="Direct link to A Revelation of Abstraction" translate="no">​</a></h3>
<p>After months of building upon <code>bun-workspaces</code>, it went from only the CLI that gave you metadata about workspaces
and ran scripts in series or parallel to a CLI and TS library with more powerful configuration and a rapidly solidifying
set of concepts.</p>
<p>As <code>bun-workspaces</code> became more advanced, I noticed that its mental model was increasingly less specific
to Bun as it started to grow as its own set of abstractions, such as "workspace patterns," its model
for the affected graph, and more.</p>
<p><a href="https://pacwich.dev/concepts/workspace-patterns" target="_blank" rel="noopener noreferrer" class="">Workspace patterns</a> used to select subsets of workspaces
in various features and config:</p>
<pre class="syntax-highlighter" style="display:block;overflow-x:auto;padding:0.75rem 1rem;color:var(--code-base-color);background:#1c1b1b;border-radius:0.5rem;background-color:var(--code-block-bg);border:1px solid rgba(40, 44, 52, 0.52);line-height:1.4;margin:0.5rem 0"><code class="language-bash" style="white-space:pre"><span style="color:var(--code-string-color)">"my-name-or-alias"</span><span> </span><span style="color:var(--code-comment-color)"># matches workspace name or alias</span><span>
</span><span></span><span style="color:var(--code-string-color)">"my-name-pattern-*"</span><span> </span><span style="color:var(--code-comment-color)"># matches workspace names only by wildcard</span><span>
</span><span></span><span style="color:var(--code-string-color)">"alias:my-alias-pattern-*"</span><span> </span><span style="color:var(--code-comment-color)"># matches workspace aliases by wildcard</span><span>
</span><span></span><span style="color:var(--code-string-color)">"path:packages/**/*"</span><span> </span><span style="color:var(--code-comment-color)"># matches workspace paths by glob</span><span>
</span><span></span><span style="color:var(--code-string-color)">"tag:my-tag"</span><span> </span><span style="color:var(--code-comment-color)"># matches workspaces with tag in config</span><span>
</span><span></span><span style="color:var(--code-string-color)">"not:my-name-or-alias"</span><span> </span><span style="color:var(--code-comment-color)"># excludes workspace name or alias</span><span>
</span><span></span><span style="color:var(--code-string-color)">"not:tag:my-tag-pattern-*"</span><span> </span><span style="color:var(--code-comment-color)"># excludes workspace tag matching wildcard</span><span>
</span><span></span><span style="color:var(--code-string-color)">"re:^my-regex-pattern$"</span><span> </span><span style="color:var(--code-comment-color)"># matches workspace name or alias by regex</span><span>
</span><span></span><span style="color:var(--code-string-color)">"not:tag:re:^my-tag-regex-pattern$"</span><span> </span><span style="color:var(--code-comment-color)"># excludes workspace tag by regex</span><span>
</span><span></span><span style="color:var(--code-string-color)">"@root"</span><span> </span><span style="color:var(--code-comment-color)"># matches the root workspace</span></code></pre>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="an-anti-lockin-philosophy">An Anti-Lockin Philosophy<a href="https://smorsic.io/blog/pacwich-launch#an-anti-lockin-philosophy" class="hash-link" aria-label="Direct link to An Anti-Lockin Philosophy" title="Direct link to An Anti-Lockin Philosophy" translate="no">​</a></h3>
<p>Part of my philosophy for <code>bun-workspaces</code> was to make it relatively unopinionated and anti-lockin. However,
its own namesake and description made it locked into one package manager.</p>
<p>Reflecting upon this, I dug deeper into whether <code>bun-workspaces</code> could retain all its present functionality
while working with <code>npm</code> or <code>pnpm</code> workspaces instead.</p>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="no-time-like-the-present">No Time Like The Present<a href="https://smorsic.io/blog/pacwich-launch#no-time-like-the-present" class="hash-link" aria-label="Direct link to No Time Like The Present" title="Direct link to No Time Like The Present" translate="no">​</a></h3>
<p>After confirming that this was a reasonable goal, I couldn't ignore the fact that if I wanted this,
putting it off and continuing development of <code>bun-workspaces</code> would mean accruing technical debt and unknowns for this pivot.</p>
<p>On top of this, more feature additions to <code>bun-workspaces</code> would mean more potential migration pain for users, so I didn't
want to waste any time. My <code>bun-workspaces</code> releases halted, where almost a month went by without a release instead of my usual
weekly cadence.</p>
<p>I am still a fan of Bun's design as a package manager and think it handles workspaces and dependency catalogs very elegantly,
and I still plan to use it personally.</p>
<p>However, I realized that decoupling my monorepo tooling from my package manager
and runtime would provide greater freedom in development, and I could not ignore this realization.</p>
<h2 class="anchor anchorTargetStickyNavbar_HeLj" id="development-strategy">Development Strategy<a href="https://smorsic.io/blog/pacwich-launch#development-strategy" class="hash-link" aria-label="Direct link to Development Strategy" title="Direct link to Development Strategy" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="ai-assistance">AI Assistance<a href="https://smorsic.io/blog/pacwich-launch#ai-assistance" class="hash-link" aria-label="Direct link to AI Assistance" title="Direct link to AI Assistance" translate="no">​</a></h3>
<p>I will get the elephant out of the room early here.</p>
<p>In a <a href="https://pacwich.dev/security" target="_blank" rel="noopener noreferrer" class="">page on <code>pacwich</code>'s security practices</a>,
I wrote at length about my approach to using AI tools for development of open source like this.</p>
<p>The quick summary is that I greatly value knowing my codebase and performing real manual review of code I'm releasing,
and I want to make it clear that I have no desire to release risky vibe code in a package that deals
with monorepo shell script orchestration and DevOps.</p>
<p>For this pivot, tools like Claude Code and the Windsurf IDE did assist my effort.</p>
<p>However, I refused to perform this launch until I had re-reviewed the entire source code, documentation,
and tests myself. I can't stand to feel like I don't know a codebase of this importance inside and out.</p>
<p>I also want to clarify that I write the documentation found at <a href="https://pacwich.dev/" target="_blank" rel="noopener noreferrer" class="">https://pacwich.dev</a>
myself. I only use agents like Claude to review what I wrote to flag potential mistakes rather than
write it for me. I personally would feel embarrassed if I couldn't describe my own spec, and this is a
way to keep myself feeling like I am the one who truly leads the project, not one of my tools.</p>
<p>If I was trying to show off how fast I could pump this pivot out, I could have easily gotten it done
in a week or less. I'm extremely glad that I didn't do this and instead spent a month ensuring it was ready
enough.</p>
<p>Luckily, while this was a lot of work, the changes to source code aren't as dramatic as they might seem,
meaning that my knowledge of <code>bun-workspaces</code>'s source code, which I developed largely manually, carried over.</p>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="things-that-stayed">Things That Stayed<a href="https://smorsic.io/blog/pacwich-launch#things-that-stayed" class="hash-link" aria-label="Direct link to Things That Stayed" title="Direct link to Things That Stayed" translate="no">​</a></h3>
<p><code>pacwich</code>'s own source monorepo retains almost exactly the same structure as <code>bun-workspaces</code>. Similarly,
<code>pacwich</code>'s package source code also retains the same general shape, with a large majority of modules carrying over.</p>
<p>In addition to this, almost all exisitng <code>bun-workspaces</code> tests remained. The overwhelming majority of test changes
other than minor Bun specifics ended up being additions of coverage.</p>
<p>Given that <code>pacwich</code> is mostly backwards compatible with <code>bun-workspaces</code>, the similarities should only be natural,
so despite its newness, it is far from brand new to me as its maintainer.</p>
<h2 class="anchor anchorTargetStickyNavbar_HeLj" id="actual-stages-of-development">Actual Stages of Development<a href="https://smorsic.io/blog/pacwich-launch#actual-stages-of-development" class="hash-link" aria-label="Direct link to Actual Stages of Development" title="Direct link to Actual Stages of Development" translate="no">​</a></h2>
<p>I'll describe my actual strategy that I took in performing this pivot, and you'll see this was by no means
anything close to a one-shot.</p>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="stage-1-make-source-code-compatible-with-nodejs">Stage 1: Make source code compatible with NodeJS<a href="https://smorsic.io/blog/pacwich-launch#stage-1-make-source-code-compatible-with-nodejs" class="hash-link" aria-label="Direct link to Stage 1: Make source code compatible with NodeJS" title="Direct link to Stage 1: Make source code compatible with NodeJS" translate="no">​</a></h3>
<p>One tradeoff of this pivot was that I could no longer rely on Bun builtins without introducing a layer
of complexity to swap out builtins based on the runtime.</p>
<p>I decided the most straightforward path would be to simply make existing source code equally compatible in
both Bun and Node without this split.</p>
<p>My goal here was to replace all Bun builtins with Node builtins that Bun also supported, until
existing tests passed with almost no modificiations and the package seemed to function normally locally.</p>
<pre class="syntax-highlighter" style="display:block;overflow-x:auto;padding:0.75rem 1rem;color:var(--code-base-color);background:#1c1b1b;border-radius:0.5rem;background-color:var(--code-block-bg);border:1px solid rgba(40, 44, 52, 0.52);line-height:1.4;margin:0.5rem 0;width:fit-content"><code class="language-bash" style="white-space:pre"><span>Bun.spawn -&gt; child_process.spawn</span></code></pre>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="stage-2-migrate-the-test-runner">Stage 2: Migrate the test runner<a href="https://smorsic.io/blog/pacwich-launch#stage-2-migrate-the-test-runner" class="hash-link" aria-label="Direct link to Stage 2: Migrate the test runner" title="Direct link to Stage 2: Migrate the test runner" translate="no">​</a></h3>
<p>This is related to the last stage, since at this point, I was using Bun's test runner and therefore
could not confirm tests passed when Node was actually used at the runtime.</p>
<p>I migrated tests to <code>vitest</code> and added scripts to run tests through Bun or Node. Actual test code generally did not
meaningfully change besides Node support changes in utilities.</p>
<pre class="syntax-highlighter" style="display:block;overflow-x:auto;padding:0.75rem 1rem;color:var(--code-base-color);background:#1c1b1b;border-radius:0.5rem;background-color:var(--code-block-bg);border:1px solid rgba(40, 44, 52, 0.52);line-height:1.4;margin:0.5rem 0"><code class="language-bash" style="white-space:pre"><span style="color:var(--code-comment-color)"># before</span><span>
</span><span>bun </span><span style="color:var(--code-built-in-color)">test</span><span>
</span>
<span></span><span style="color:var(--code-comment-color)"># after</span><span>
</span><span>npx vitest </span><span style="color:var(--code-comment-color)"># use Node.js runtime</span><span>
</span><span>bunx --bun vitest </span><span style="color:var(--code-comment-color)"># force Bun runtime</span></code></pre>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="stage-3-design-an-adapter-layer-to-hide-bun-details">Stage 3: Design an adapter layer to hide Bun details<a href="https://smorsic.io/blog/pacwich-launch#stage-3-design-an-adapter-layer-to-hide-bun-details" class="hash-link" aria-label="Direct link to Stage 3: Design an adapter layer to hide Bun details" title="Direct link to Stage 3: Design an adapter layer to hide Bun details" translate="no">​</a></h3>
<p>Before jumping to <code>npm</code> and <code>pnpm</code> support, it made sense to first extract all behavior specific to Bun integration
into a new layer that would abstract away the package manager.</p>
<p>This way I could prepare the multi-package support while confirming that the existing tests passed for what was
still essentially <code>bun-workspaces</code> at this point.</p>
<pre class="syntax-highlighter" style="display:block;overflow-x:auto;padding:0.75rem 1rem;color:var(--code-base-color);background:#1c1b1b;border-radius:0.5rem;background-color:var(--code-block-bg);border:1px solid rgba(40, 44, 52, 0.52);line-height:1.4;margin:0.5rem 0;width:fit-content"><code class="language-typescript" style="white-space:pre"><span>parseBunLock() -&gt; createAdapter(</span><span style="color:var(--code-string-color)">"bun"</span><span>).parseLockfile()</span></code></pre>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="stage-4-create-a-testing-layer-for-the-adapter-layer">Stage 4: Create a testing layer for the adapter layer<a href="https://smorsic.io/blog/pacwich-launch#stage-4-create-a-testing-layer-for-the-adapter-layer" class="hash-link" aria-label="Direct link to Stage 4: Create a testing layer for the adapter layer" title="Direct link to Stage 4: Create a testing layer for the adapter layer" translate="no">​</a></h3>
<p>To further prepare for <code>npm</code> and <code>pnpm</code> support, it made sense to go ahead and add tests
that would iterate over each supported pm to confrim conformancy of behavior, ensuring
that behavior was the same across the CLI and API for the same set of test projects.</p>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="stage-5-add-support-for-npm-workspaces">Stage 5: Add support for <code>npm</code> workspaces<a href="https://smorsic.io/blog/pacwich-launch#stage-5-add-support-for-npm-workspaces" class="hash-link" aria-label="Direct link to stage-5-add-support-for-npm-workspaces" title="Direct link to stage-5-add-support-for-npm-workspaces" translate="no">​</a></h3>
<p>Now that an adapter existed with conformancy tests, adding pm support just meant
making a new adapter implementation.</p>
<p>This was the natural first choice due to <code>npm</code>'s simplicity and the fact that
its workspace configuration is very similar to Bun's, using <code>package.json["workspaces"]</code>.</p>
<p>However, this addition inspired the <a href="https://pacwich.dev/cli/commands#verify" target="_blank" rel="noopener noreferrer" class=""><code>verify</code> feature</a> used to help ensure
workspace-to-workspace dependencies are explicitly defined in workspace <code>package.json</code> files,
something <code>npm</code> does not require, unlike Bun and <code>pnpm</code>.</p>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="stage-6-add-support-for-pnpm-workspaces">Stage 6: Add support for <code>pnpm</code> workspaces<a href="https://smorsic.io/blog/pacwich-launch#stage-6-add-support-for-pnpm-workspaces" class="hash-link" aria-label="Direct link to stage-6-add-support-for-pnpm-workspaces" title="Direct link to stage-6-add-support-for-pnpm-workspaces" translate="no">​</a></h3>
<p>This was the obvious next step, which mainly involved adding in the YAML parsing and logic needed
to read <code>pnpm-workspace.yaml</code> and <code>pnpm-lock.yaml</code>.</p>
<pre class="syntax-highlighter" style="display:block;overflow-x:auto;padding:0.75rem 1rem;color:var(--code-base-color);background:#1c1b1b;border-radius:0.5rem;background-color:var(--code-block-bg);border:1px solid rgba(40, 44, 52, 0.52);line-height:1.4;margin:0.5rem 0"><code class="language-typescript" style="white-space:pre"><span style="color:var(--code-comment-color)">// Hooray!</span><span>
</span><span></span><span style="color:var(--code-keyword-color)">export</span><span> </span><span style="color:var(--code-keyword-color)">const</span><span> PACKAGE_MANAGER_NAMES = [</span><span style="color:var(--code-string-color)">"bun"</span><span>, </span><span style="color:var(--code-string-color)">"pnpm"</span><span>, </span><span style="color:var(--code-string-color)">"npm"</span><span>] </span><span style="color:var(--code-keyword-color)">as</span><span> </span><span style="color:var(--code-keyword-color)">const</span><span>;
</span><span></span><span style="color:var(--code-keyword-color)">export</span><span> </span><span style="color:var(--code-keyword-color)">type</span><span> PackageManagerName = (</span><span style="color:var(--code-keyword-color)">typeof</span><span> PACKAGE_MANAGER_NAMES)[</span><span style="color:var(--code-built-in-color)">number</span><span>];</span></code></pre>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="stage-7-desired-deprecations-and-small-breaking-changes-from-bun-workspaces">Stage 7: Desired deprecations and small breaking changes from <code>bun-workspaces</code><a href="https://smorsic.io/blog/pacwich-launch#stage-7-desired-deprecations-and-small-breaking-changes-from-bun-workspaces" class="hash-link" aria-label="Direct link to stage-7-desired-deprecations-and-small-breaking-changes-from-bun-workspaces" title="Direct link to stage-7-desired-deprecations-and-small-breaking-changes-from-bun-workspaces" translate="no">​</a></h3>
<p>Since this is the launch of a brand new package, I took the opportunity for some minor
breaking cleanup items described in <a href="https://pacwich.dev/intro/bun-workspaces-migration" target="_blank" rel="noopener noreferrer" class="">the migration guide</a>.</p>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="stage-8-feature-additions">Stage 8: Feature additions<a href="https://smorsic.io/blog/pacwich-launch#stage-8-feature-additions" class="hash-link" aria-label="Direct link to Stage 8: Feature additions" title="Direct link to Stage 8: Feature additions" translate="no">​</a></h3>
<p>Mainly due to <code>npm</code>'s lack of requirement for explicit workspace dependencies,
the <a href="https://pacwich.dev/cli/commands#verify" target="_blank" rel="noopener noreferrer" class=""><code>verify</code></a> feature was designed
to help detect workspaces that imported/exported from workspaces not declared
as dependencies in <code>package.json</code>.</p>
<p>AI integrations had some improvements, while most MCP tools were removed,
since they were too redundant and limited compared to CLI, placing emphasis
on using MCP resources to read the package overview and CLI docs.</p>
<p>A CLI feature to <a href="https://pacwich.dev/ai/skills" target="_blank" rel="noopener noreferrer" class="">add skill files</a> was
added as a quick win for another means of providing docs to an agent.</p>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="stage-9-documentation-and-final-manual-reviews">Stage 9: Documentation and Final Manual Reviews<a href="https://smorsic.io/blog/pacwich-launch#stage-9-documentation-and-final-manual-reviews" class="hash-link" aria-label="Direct link to Stage 9: Documentation and Final Manual Reviews" title="Direct link to Stage 9: Documentation and Final Manual Reviews" translate="no">​</a></h3>
<p>This was actually the longest of all these stages. As I described earlier,
I rewrote the old bun-workspaces documentation website into <a href="https://pacwich.dev/" target="_blank" rel="noopener noreferrer" class="">pacwich.dev</a>,
myself, which was worth it.</p>
<p>In addition to multiple agent passes to help with review, I also conducted
a full unassisted manual review of the codebase myself to regain confidence
in my source code knowledge, since a lot of details had changed.</p>
<p>Even though I already reviewed as I went through each stage, this was one last
deep dive where I caught code smells, adjusted code to match style and naming
consistency, found concerns to refactor for DRYness and the like, polished tests,
removed long comments where better naming would suffice, and more.</p>
<p>I also had a lot of other preparations to do, like writing this very blog post.</p>
<pre class="syntax-highlighter" style="display:block;overflow-x:auto;padding:0.75rem 1rem;color:var(--code-base-color);background:#1c1b1b;border-radius:0.5rem;background-color:var(--code-block-bg);border:1px solid rgba(40, 44, 52, 0.52);line-height:1.4;margin:0.5rem 0"><code class="language-typescript" style="white-space:pre"><span style="color:var(--code-comment-color)">/** </span><span style="color:#88aece">@todo </span><span style="color:var(--code-comment-color)">plan changes for every single thing bun-workspaces ever touched */</span></code></pre>
<h2 class="anchor anchorTargetStickyNavbar_HeLj" id="conclusion">Conclusion<a href="https://smorsic.io/blog/pacwich-launch#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" translate="no">​</a></h2>
<p>If you have read this as someone who was already a fan of <code>bun-workspaces</code>,
I hope that you feel as optimistic as I do that this is a net win for the project,
as almost nothing has been lost from <code>bun-workspaces</code> while several new advantages
not possible before have been gained thanks to runtime and package manager portability.</p>
<p>While <code>pacwich</code> is at <code>0.x</code> at launch, I plan to guard stability as much as I can.,
treating it more like a <code>1.x</code> or higher version without a very compelling
reason for any breakage.</p>]]></content:encoded>
            <category>Release</category>
            <category>pacwich</category>
        </item>
        <item>
            <title><![CDATA[bun-workspaces: v1.9 - An Affected Graph is Here]]></title>
            <link>https://smorsic.io/blog/bun-workspaces-v1_9</link>
            <guid>https://smorsic.io/blog/bun-workspaces-v1_9</guid>
            <pubDate>Thu, 07 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[⚠️ UPDATE: bun-workspaces is now developed as]]></description>
            <content:encoded><![CDATA[<p><b>⚠️ UPDATE:</b> <code>bun-workspaces</code> is now developed as
<a href="https://pacwich.dev/" target="_blank" rel="noopener noreferrer" class=""><code>pacwich</code></a>, which supports Bun, npm, and pnpm workspaces.</p>
<div class="center"><img src="https://smorsic.io/blog/images/affected-bwunster_64x120.png" width="128" class="pixel-art-image  undefined"></div>
<br>
<p><code>bun-workspaces</code> 1.9 is a major milestone for the project, thanks to the new <strong>debuggable</strong> affected graph!</p>
<p>Read on for more details, or get straight into the <a href="https://bunworkspaces.com/concepts/affected" target="_blank" rel="noopener noreferrer" class="">documentation</a>.</p>
<p>More documentation resources:</p>
<ul>
<li class=""><a href="https://bunworkspaces.com/concepts/affected" target="_blank" rel="noopener noreferrer" class="">Affected Overview</a></li>
<li class="">CLI:<!-- -->
<ul>
<li class=""><a href="https://bunworkspaces.com/cli/commands#list-affected" target="_blank" rel="noopener noreferrer" class=""><code>bw list-affected</code></a></li>
<li class=""><a href="https://bunworkspaces.com/cli/commands#run-affected" target="_blank" rel="noopener noreferrer" class=""><code>bw run-affected</code></a></li>
</ul>
</li>
<li class="">TS/JS API:<!-- -->
<ul>
<li class=""><a href="https://bunworkspaces.com/api/reference#determineaffectedworkspaces" target="_blank" rel="noopener noreferrer" class=""><code>FileSystemProject.determineAffectedWorkspaces()</code></a></li>
<li class=""><a href="https://bunworkspaces.com/api/reference#runaffectedworkspacescript" target="_blank" rel="noopener noreferrer" class=""><code>FileSystemProject.runAffectedWorkspaceScript()</code></a></li>
</ul>
</li>
<li class="">Configuration<!-- -->
<ul>
<li class=""><a href="https://bunworkspaces.com/config/root" target="_blank" rel="noopener noreferrer" class="">Project Root Configuration</a> (sets default base git ref for comparison)</li>
<li class=""><a href="https://bunworkspaces.com/config/workspace" target="_blank" rel="noopener noreferrer" class="">Workspace Configuration</a> (sets inputs for affected detection)</li>
<li class=""><a href="https://bunworkspaces.com/config/workspace-inputs" target="_blank" rel="noopener noreferrer" class="">Workspace Inputs</a> (configuring affected detection inputs)</li>
</ul>
</li>
<li class=""><a href="https://bunworkspaces.com/concepts/workspace-dependencies" target="_blank" rel="noopener noreferrer" class="">Workspace Dependencies</a> (how workspaces depend on each other)</li>
</ul>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_HeLj" id="graphs">Graphs?<a href="https://smorsic.io/blog/bun-workspaces-v1_9#graphs" class="hash-link" aria-label="Direct link to Graphs?" title="Direct link to Graphs?" translate="no">​</a></h2>
<p>The "graph" term hasn't been explicitly used with <code>bun-workspaces</code> primitives and features very much, but it's a useful
concept that still applies.</p>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="the-project-graph">The Project Graph<a href="https://smorsic.io/blog/bun-workspaces-v1_9#the-project-graph" class="hash-link" aria-label="Direct link to The Project Graph" title="Direct link to The Project Graph" translate="no">​</a></h3>
<p>In <code>bun-workspaces</code>, you could say that the <strong>project graph</strong> is simply the structure of your
workspaces and how they depend on each other, each workspace being a node and each dependency being an edge.</p>
<p><code>bun-workspaces</code> does <strong>not</strong> perform source code analysis to determine the graph, since with Bun,
workspaces must be explicitly declared in <code>package.json</code> dependencies like <code>"my-workspace-name": "workspace:*"</code>
for imports/exports to work between them.</p>
<p>This makes determining the project graph fast and computationally cheap.</p>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="the-affected-graph">The Affected Graph<a href="https://smorsic.io/blog/bun-workspaces-v1_9#the-affected-graph" class="hash-link" aria-label="Direct link to The Affected Graph" title="Direct link to The Affected Graph" translate="no">​</a></h3>
<p>The <strong>affected graph</strong> is simply only the part of the project graph that has been <em>meaningfully changed</em>.</p>
<p>If one can determine which workspaces are affected by some code change, that can be very helpful for orchestrating
DevOps tasks, such as running builds for only those changed workspaces.</p>
<h2 class="anchor anchorTargetStickyNavbar_HeLj" id="what-is-a-meaningful-change">What is a Meaningful Change?<a href="https://smorsic.io/blog/bun-workspaces-v1_9#what-is-a-meaningful-change" class="hash-link" aria-label="Direct link to What is a Meaningful Change?" title="Direct link to What is a Meaningful Change?" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="inputs-the-source-of-changes">Inputs: The Source of Changes<a href="https://smorsic.io/blog/bun-workspaces-v1_9#inputs-the-source-of-changes" class="hash-link" aria-label="Direct link to Inputs: The Source of Changes" title="Direct link to Inputs: The Source of Changes" translate="no">​</a></h3>
<p>By default, a workspace is considered to have a set of <strong>inputs</strong>.</p>
<p>When not configured, the default inputs are:</p>
<ul>
<li class="">All git-trackable files in the workspace's directory</li>
<li class="">All workspace dependencies. If a workspace dependency is affected for any reason, its dependents are also considered affected.</li>
<li class="">The version resolved in <code>bun.lock</code> for all other dependencies in a workspace's <code>package.json</code>.</li>
</ul>
<p>Inputs can be configured per script as well.</p>
<p>Here's an example of a workspace config that configures its inputs:</p>
<pre class="syntax-highlighter" style="display:block;overflow-x:auto;padding:0.75rem 1rem;color:var(--code-base-color);background:#1c1b1b;border-radius:0.5rem;background-color:var(--code-block-bg);border:1px solid rgba(40, 44, 52, 0.52);line-height:1.4;margin:0.5rem 0"><code class="language-typescript" style="white-space:pre"><span style="color:var(--code-comment-color)">// path/to/your/workspace/bw.workspace.ts</span><span>
</span>
<span></span><span style="color:var(--code-keyword-color)">import</span><span> { defineWorkspaceConfig } </span><span style="color:var(--code-keyword-color)">from</span><span> </span><span style="color:var(--code-string-color)">"bun-workspaces/config"</span><span>;
</span>
<span></span><span style="color:var(--code-keyword-color)">export</span><span> </span><span style="color:var(--code-keyword-color)">default</span><span> defineWorkspaceConfig({
</span><span>  </span><span style="color:var(--code-attr-color)">alias</span><span>: </span><span style="color:var(--code-string-color)">"my-web-app"</span><span>,
</span><span>  </span><span style="color:var(--code-attr-color)">tags</span><span>: [</span><span style="color:var(--code-string-color)">"frontend"</span><span>],
</span><span>  </span><span style="color:var(--code-comment-color)">// All input config is optional</span><span>
</span><span>  </span><span style="color:var(--code-attr-color)">defaultInputs</span><span>: {
</span><span>    </span><span style="color:var(--code-comment-color)">// default: all git-trackable files in the workspace directory</span><span>
</span><span>    </span><span style="color:var(--code-attr-color)">files</span><span>: [
</span><span>      </span><span style="color:var(--code-string-color)">"src/**/*.{ts,tsx}"</span><span>,
</span><span>      </span><span style="color:var(--code-string-color)">"!src/**/*.test.{ts,tsx}"</span><span>,
</span><span>      </span><span style="color:var(--code-string-color)">"/tsconfig.json"</span><span>, </span><span style="color:var(--code-comment-color)">// relative to project root</span><span>
</span>    ],
<span>    </span><span style="color:var(--code-comment-color)">// treat all tagged libs like dependencies</span><span>
</span><span>    </span><span style="color:var(--code-attr-color)">workspacePatterns</span><span>: [</span><span style="color:var(--code-string-color)">"tag:lib"</span><span>],
</span><span>    </span><span style="color:var(--code-comment-color)">// treat only the "react" package as affect-able</span><span>
</span><span>    </span><span style="color:var(--code-attr-color)">externalDependencies</span><span>: [</span><span style="color:var(--code-string-color)">"react"</span><span>],
</span>  },
<span>  </span><span style="color:var(--code-attr-color)">scripts</span><span>: {
</span><span>    </span><span style="color:var(--code-comment-color)">// configure inputs per script</span><span>
</span><span>    </span><span style="color:var(--code-attr-color)">test</span><span>: {
</span><span>      </span><span style="color:var(--code-attr-color)">inputs</span><span>: {
</span><span>        </span><span style="color:var(--code-attr-color)">files</span><span>: [</span><span style="color:var(--code-string-color)">"src/**/*.{ts,tsx}"</span><span>],
</span>      },
<!-- -->    },
<!-- -->  },
<!-- -->});</code></pre>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="detecting-changes">Detecting Changes<a href="https://smorsic.io/blog/bun-workspaces-v1_9#detecting-changes" class="hash-link" aria-label="Direct link to Detecting Changes" title="Direct link to Detecting Changes" translate="no">​</a></h3>
<p>There are two main ways to determine affected workspaces:</p>
<h4 class="anchor anchorTargetStickyNavbar_HeLj" id="using-git-diff-default">Using git diff (default)<a href="https://smorsic.io/blog/bun-workspaces-v1_9#using-git-diff-default" class="hash-link" aria-label="Direct link to Using git diff (default)" title="Direct link to Using git diff (default)" translate="no">​</a></h4>
<p>The default behavior of affected features is to use the git diff between the current HEAD and the default base ref in git.</p>
<p>The default base ref is <code>main</code> except when overridden via the project root config or a environment variable <code>BW_AFFECTED_BASE_REF_DEFAULT</code>.</p>
<p>Uncommitted changes (staged, unstaged, untracked) are considered a part of the diff by default.
Files that are gitignored are never considered a part of the diff.</p>
<pre class="syntax-highlighter" style="display:block;overflow-x:auto;padding:0.75rem 1rem;color:var(--code-base-color);background:#1c1b1b;border-radius:0.5rem;background-color:var(--code-block-bg);border:1px solid rgba(40, 44, 52, 0.52);line-height:1.4;margin:0.5rem 0"><code class="language-bash" style="white-space:pre"><span style="color:var(--code-comment-color)"># Uses diff of current HEAD against the default base ref</span><span>
</span>bw ls-affected
<!-- -->
<span></span><span style="color:var(--code-comment-color)"># Compare specific refs (can also pass commit SHA, tag, etc.)</span><span>
</span>bw ls-affected --base=my-branch-a --head=my-branch-b
<!-- -->
<span></span><span style="color:var(--code-comment-color)"># Ignore uncommitted changes</span><span>
</span><span>bw ls-affected --ignore-uncommitted </span><span style="color:var(--code-comment-color)"># (staged, unstaged and untracked)</span><span>
</span>bw ls-affected --ignore-untracked
<!-- -->bw ls-affected --ignore-unstaged
<!-- -->bw ls-affected --ignore-staged
<!-- -->
<span></span><span style="color:var(--code-comment-color)"># Ignore changes to workspace dependencies derived from package.json files</span><span>
</span>bw ls-affected --ignore-workspace-deps
<!-- -->
<span></span><span style="color:var(--code-comment-color)"># Ignore changes to external dependencies (e.g. npm packages)</span><span>
</span>bw ls-affected --ignore-external-deps</code></pre>
<h4 class="anchor anchorTargetStickyNavbar_HeLj" id="using-a-list-of-changed-files">Using a list of changed files<a href="https://smorsic.io/blog/bun-workspaces-v1_9#using-a-list-of-changed-files" class="hash-link" aria-label="Direct link to Using a list of changed files" title="Direct link to Using a list of changed files" translate="no">​</a></h4>
<p>You can bypass using git entirely and simply pass a list of changed files that match workspace inputs.</p>
<pre class="syntax-highlighter" style="display:block;overflow-x:auto;padding:0.75rem 1rem;color:var(--code-base-color);background:#1c1b1b;border-radius:0.5rem;background-color:var(--code-block-bg);border:1px solid rgba(40, 44, 52, 0.52);line-height:1.4;margin:0.5rem 0"><code class="language-bash" style="white-space:pre"><span>bw ls-affected --files=</span><span style="color:var(--code-string-color)">"packages/example/**/*.ts packages/example/my-file.js"</span></code></pre>
<h2 class="anchor anchorTargetStickyNavbar_HeLj" id="debuggability">Debuggability<a href="https://smorsic.io/blog/bun-workspaces-v1_9#debuggability" class="hash-link" aria-label="Direct link to Debuggability" title="Direct link to Debuggability" translate="no">​</a></h2>
<p>You can use the <code>--explain</code> flag to get detailed reasoning for the affected workspaces.</p>
<pre class="syntax-highlighter" style="display:block;overflow-x:auto;padding:0.75rem 1rem;color:var(--code-base-color);background:#1c1b1b;border-radius:0.5rem;background-color:var(--code-block-bg);border:1px solid rgba(40, 44, 52, 0.52);line-height:1.4;margin:0.5rem 0"><code class="language-bash" style="white-space:pre"><span style="color:var(--code-comment-color)"># Detailed output about every changed file and dependency</span><span>
</span>bw ls-affected --explain --detailed</code></pre>
<p>This can give you output such as this when <code>workspace-b</code> depends on <code>workspace-a</code>, whose <code>my-script.ts</code> file is changed:</p>
<div class="language-text codeBlockContainer_KAtS theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_lBp1"><pre tabindex="0" class="prism-code language-text codeBlock_VsFy thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_vKUN"><div class="token-line" style="color:#F8F8F2"><span class="token plain">Workspace: workspace-a</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Path: packages/workspace-a</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Changed input files:</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"> - my-script.ts (input: "my-script.ts")</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Affected dependencies: (none)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Changed external dependencies: (none)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Workspace: workspace-b</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Path: packages/workspace-b</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Changed input files: (none)</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">Affected dependencies:</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">- workspace-a</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  chain: workspace-b --[package]-&gt; workspace-a</span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  Changed external dependencies: (none)</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_HeLj" id="running-affected-scripts">Running Affected Scripts<a href="https://smorsic.io/blog/bun-workspaces-v1_9#running-affected-scripts" class="hash-link" aria-label="Direct link to Running Affected Scripts" title="Direct link to Running Affected Scripts" translate="no">​</a></h2>
<p>You can run a script across all affected workspaces with the <code>bw run-affected</code> command.</p>
<p>It essentially marries the options of <code>bw ls-affected</code> and <code>bw run</code>.</p>
<pre class="syntax-highlighter" style="display:block;overflow-x:auto;padding:0.75rem 1rem;color:var(--code-base-color);background:#1c1b1b;border-radius:0.5rem;background-color:var(--code-block-bg);border:1px solid rgba(40, 44, 52, 0.52);line-height:1.4;margin:0.5rem 0"><code class="language-bash" style="white-space:pre"><span>bw run-affected my-script --base=my-branch --ignore-uncommitted</span></code></pre>
<h2 class="anchor anchorTargetStickyNavbar_HeLj" id="api-parity">API Parity<a href="https://smorsic.io/blog/bun-workspaces-v1_9#api-parity" class="hash-link" aria-label="Direct link to API Parity" title="Direct link to API Parity" translate="no">​</a></h2>
<p>As usual, the TypeScript API is in parity with the CLI.</p>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="determine-affected-workspaces-via-git">Determine affected workspaces via git:<a href="https://smorsic.io/blog/bun-workspaces-v1_9#determine-affected-workspaces-via-git" class="hash-link" aria-label="Direct link to Determine affected workspaces via git:" title="Direct link to Determine affected workspaces via git:" translate="no">​</a></h3>
<pre class="syntax-highlighter" style="display:block;overflow-x:auto;padding:0.75rem 1rem;color:var(--code-base-color);background:#1c1b1b;border-radius:0.5rem;background-color:var(--code-block-bg);border:1px solid rgba(40, 44, 52, 0.52);line-height:1.4;margin:0.5rem 0"><code class="language-typescript" style="white-space:pre"><span style="color:var(--code-keyword-color)">import</span><span> { createFileSystemProject } </span><span style="color:var(--code-keyword-color)">from</span><span> </span><span style="color:var(--code-string-color)">"bun-workspaces"</span><span>;
</span>
<span></span><span style="color:var(--code-comment-color)">// Initialize Project, by default at process.cwd()</span><span>
</span><span></span><span style="color:var(--code-keyword-color)">const</span><span> myProject = createFileSystemProject();
</span>
<span></span><span style="color:var(--code-comment-color)">// Workspace results contain details about each workspace,</span><span>
</span><span></span><span style="color:var(--code-comment-color)">// including whether it is affected and why</span><span>
</span><span></span><span style="color:var(--code-keyword-color)">const</span><span> { workspaceResults } = </span><span style="color:var(--code-keyword-color)">await</span><span> project.determineAffectedWorkspaces({
</span><span>  </span><span style="color:var(--code-comment-color)">// Optional, the script to run to determine affected workspaces</span><span>
</span><span>  </span><span style="color:var(--code-comment-color)">// When not provided, based on workspaces' default inputs</span><span>
</span><span>  </span><span style="color:var(--code-attr-color)">script</span><span>: </span><span style="color:var(--code-string-color)">"my-script"</span><span>,
</span><span>  </span><span style="color:var(--code-attr-color)">diffSource</span><span>: </span><span style="color:var(--code-string-color)">"git"</span><span>,
</span><span>  </span><span style="color:var(--code-attr-color)">diffOptions</span><span>: {
</span><span>    </span><span style="color:var(--code-comment-color)">// Optional, defaults to main if default base ref not configured</span><span>
</span><span>    </span><span style="color:var(--code-attr-color)">baseRef</span><span>: </span><span style="color:var(--code-string-color)">"my-branch-a"</span><span>,
</span><span>    </span><span style="color:var(--code-comment-color)">// Optional, defaults to current HEAD if not provided</span><span>
</span><span>    </span><span style="color:var(--code-attr-color)">headRef</span><span>: </span><span style="color:var(--code-string-color)">"my-branch-b"</span><span>,
</span>
<span>    </span><span style="color:var(--code-comment-color)">// Optional means of ignoring uncommitted changes</span><span>
</span><span>    </span><span style="color:var(--code-comment-color)">// gitignored files are never included in a diff</span><span>
</span><span>    </span><span style="color:var(--code-attr-color)">ignoreUntracked</span><span>: </span><span style="color:#f08d49">false</span><span>, </span><span style="color:var(--code-comment-color)">// files that may be tracked but aren't</span><span>
</span><span>    </span><span style="color:var(--code-attr-color)">ignoreStaged</span><span>: </span><span style="color:#f08d49">false</span><span>,
</span><span>    </span><span style="color:var(--code-attr-color)">ignoreUnstaged</span><span>: </span><span style="color:#f08d49">false</span><span>,
</span><span>    </span><span style="color:var(--code-comment-color)">// Ignores untracked, staged, and unstaged</span><span>
</span><span>    </span><span style="color:var(--code-attr-color)">ignoreUncommitted</span><span>: </span><span style="color:#f08d49">false</span><span>,
</span>  },
<span>  </span><span style="color:var(--code-comment-color)">// Ignore workspace dependencies when determining affected workspaces</span><span>
</span><span>  </span><span style="color:var(--code-attr-color)">ignoreWorkspaceDependencies</span><span>: </span><span style="color:#f08d49">false</span><span>,
</span><span>  </span><span style="color:var(--code-comment-color)">// Ignore changes external dependencies (e.g. react, lodash) lock versions</span><span>
</span><span>  </span><span style="color:var(--code-attr-color)">ignoreExternalDependencies</span><span>: </span><span style="color:#f08d49">false</span><span>,
</span>});</code></pre>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="determine-affected-workspaces-via-list-of-files">Determine affected workspaces via list of files:<a href="https://smorsic.io/blog/bun-workspaces-v1_9#determine-affected-workspaces-via-list-of-files" class="hash-link" aria-label="Direct link to Determine affected workspaces via list of files:" title="Direct link to Determine affected workspaces via list of files:" translate="no">​</a></h3>
<pre class="syntax-highlighter" style="display:block;overflow-x:auto;padding:0.75rem 1rem;color:var(--code-base-color);background:#1c1b1b;border-radius:0.5rem;background-color:var(--code-block-bg);border:1px solid rgba(40, 44, 52, 0.52);line-height:1.4;margin:0.5rem 0"><code class="language-typescript" style="white-space:pre"><span style="color:var(--code-keyword-color)">import</span><span> { createFileSystemProject } </span><span style="color:var(--code-keyword-color)">from</span><span> </span><span style="color:var(--code-string-color)">"bun-workspaces"</span><span>;
</span>
<span></span><span style="color:var(--code-keyword-color)">const</span><span> myProject = createFileSystemProject();
</span>
<span></span><span style="color:var(--code-keyword-color)">const</span><span> { workspaceResults } = </span><span style="color:var(--code-keyword-color)">await</span><span> project.determineAffectedWorkspaces({
</span><span>  </span><span style="color:var(--code-comment-color)">// Bypass git and pass a list of changed files that match workspace inputs</span><span>
</span><span>  </span><span style="color:var(--code-attr-color)">diffSource</span><span>: </span><span style="color:var(--code-string-color)">"fileList"</span><span>,
</span><span>  </span><span style="color:var(--code-attr-color)">changedFiles</span><span>: [</span><span style="color:var(--code-string-color)">"src/**/*.ts"</span><span>, </span><span style="color:var(--code-string-color)">"something.txt"</span><span>],
</span>});</code></pre>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="run-a-script-across-affected-workspaces">Run a script across affected workspaces:<a href="https://smorsic.io/blog/bun-workspaces-v1_9#run-a-script-across-affected-workspaces" class="hash-link" aria-label="Direct link to Run a script across affected workspaces:" title="Direct link to Run a script across affected workspaces:" translate="no">​</a></h3>
<pre class="syntax-highlighter" style="display:block;overflow-x:auto;padding:0.75rem 1rem;color:var(--code-base-color);background:#1c1b1b;border-radius:0.5rem;background-color:var(--code-block-bg);border:1px solid rgba(40, 44, 52, 0.52);line-height:1.4;margin:0.5rem 0"><code class="language-typescript" style="white-space:pre"><span style="color:var(--code-keyword-color)">import</span><span> { createFileSystemProject } </span><span style="color:var(--code-keyword-color)">from</span><span> </span><span style="color:var(--code-string-color)">"bun-workspaces"</span><span>;
</span>
<span></span><span style="color:var(--code-keyword-color)">const</span><span> myProject = createFileSystemProject();
</span>
<span></span><span style="color:var(--code-comment-color)">// Returns the same output and summary as project.runScriptAcrossWorkspaces</span><span>
</span><span></span><span style="color:var(--code-keyword-color)">const</span><span> { output, summary } = </span><span style="color:var(--code-keyword-color)">await</span><span> project.runAffectedWorkspaceScript({
</span><span>  </span><span style="color:var(--code-comment-color)">// About the same options as project.determineAffectedWorkspaces</span><span>
</span><span>  </span><span style="color:var(--code-attr-color)">affectedOptions</span><span>: {
</span><span>    </span><span style="color:var(--code-attr-color)">diffSource</span><span>: </span><span style="color:var(--code-string-color)">"git"</span><span>,
</span><span>    </span><span style="color:var(--code-attr-color)">diffOptions</span><span>: {
</span><span>      </span><span style="color:var(--code-attr-color)">baseRef</span><span>: </span><span style="color:var(--code-string-color)">"my-branch-a"</span><span>,
</span><span>      </span><span style="color:var(--code-attr-color)">headRef</span><span>: </span><span style="color:var(--code-string-color)">"my-branch-b"</span><span>,
</span>    },
<!-- -->  },
<span>  </span><span style="color:var(--code-comment-color)">// About the same options as project.runScriptAcrossWorkspaces</span><span>
</span><span>  </span><span style="color:var(--code-attr-color)">scriptOptions</span><span>: {
</span><span>    </span><span style="color:var(--code-attr-color)">script</span><span>: </span><span style="color:var(--code-string-color)">"my-script"</span><span>,
</span>  },
<!-- -->});</code></pre>
<h2 class="anchor anchorTargetStickyNavbar_HeLj" id="conclusion">Conclusion<a href="https://smorsic.io/blog/bun-workspaces-v1_9#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" translate="no">​</a></h2>
<p>This is an exciting and big improvement to <code>bun-workspaces</code> I've wanted since day one, but I wanted
to ensure that the right foundation was in place for it first.</p>
<p>This is just one of several features to come that add advanced monorepo management capabilities to <code>bun-workspaces</code>
without changing the fact that you can use the package right away without any configuration.</p>
<p>This continues to reinforce my hypothesis that powerful monorepos can be achieved with more lightweight tooling
that works with, rather than against, native tooling.</p>]]></content:encoded>
            <category>Release</category>
            <category>bun-workspaces</category>
        </item>
        <item>
            <title><![CDATA[bun-workspaces: v1.5 - Workspace Tags, Dependency Rules, TypeScript Config Files, An MCP Server]]></title>
            <link>https://smorsic.io/blog/bun-workspaces-v1_5</link>
            <guid>https://smorsic.io/blog/bun-workspaces-v1_5</guid>
            <pubDate>Fri, 10 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[⚠️ UPDATE: bun-workspaces is now developed as]]></description>
            <content:encoded><![CDATA[<p><b>⚠️ UPDATE:</b> <code>bun-workspaces</code> is now developed as
<a href="https://pacwich.dev/" target="_blank" rel="noopener noreferrer" class=""><code>pacwich</code></a>, which supports Bun, npm, and pnpm workspaces.</p>
<div class="center"><img src="https://smorsic.io/blog/images/bwunster-v1.5_134x102.png" width="268" class="pixel-art-image  undefined"></div>
<br>
<p><code>bun-workspaces</code> 1.5 is here! Let's cover some noteworthy feature additions since
<a class="" href="https://smorsic.io/blog/bun-workspaces-v1">version 1</a>'s release.</p>
<p>Jump to: <a href="https://smorsic.io/blog/bun-workspaces-v1_5#typescriptjavascript-config-files" class="">TypeScript Configs</a> |
<a href="https://smorsic.io/blog/bun-workspaces-v1_5#workspace-tags" class="">Workspace Tags</a> | <a href="https://smorsic.io/blog/bun-workspaces-v1_5#workspace-dependency-rules" class="">Dependency Rules</a> | <a href="https://smorsic.io/blog/bun-workspaces-v1_5#mcp-server" class="">MCP Server</a></p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_HeLj" id="typescriptjavascript-config-files">TypeScript/JavaScript Config Files<a href="https://smorsic.io/blog/bun-workspaces-v1_5#typescriptjavascript-config-files" class="hash-link" aria-label="Direct link to TypeScript/JavaScript Config Files" title="Direct link to TypeScript/JavaScript Config Files" translate="no">​</a></h2>
<p><code>bun-workspaces</code>'s optional <a href="https://bunworkspaces.com/config" target="_blank" rel="noopener noreferrer" class="">config files</a> were only able to be written in JSON/JSONC
previously.</p>
<p>Now, config files can be written in TypeScript or JavaScript, such as in <code>bw.workspace.ts</code>:</p>
<pre class="syntax-highlighter" style="display:block;overflow-x:auto;padding:0.75rem 1rem;color:var(--code-base-color);background:#1c1b1b;border-radius:0.5rem;background-color:var(--code-block-bg);border:1px solid rgba(40, 44, 52, 0.52);line-height:1.4;margin:0.5rem 0"><code class="language-typescript" style="white-space:pre"><span style="color:var(--code-keyword-color)">import</span><span> { defineWorkspaceConfig } </span><span style="color:var(--code-keyword-color)">from</span><span> </span><span style="color:var(--code-string-color)">"bun-workspaces/config"</span><span>;
</span>
<span></span><span style="color:var(--code-keyword-color)">export</span><span> </span><span style="color:var(--code-keyword-color)">default</span><span> defineWorkspaceConfig({
</span><span>  </span><span style="color:var(--code-attr-color)">alias</span><span>: </span><span style="color:var(--code-string-color)">"my-web-app"</span><span>,
</span><span>  </span><span style="color:var(--code-attr-color)">tags</span><span>: [</span><span style="color:var(--code-string-color)">"frontend"</span><span>],
</span><span>  </span><span style="color:var(--code-attr-color)">rules</span><span>: {
</span><span>    </span><span style="color:var(--code-attr-color)">workspaceDependencies</span><span>: {
</span><span>      </span><span style="color:var(--code-attr-color)">denyPatterns</span><span>: [
</span><span>        </span><span style="color:var(--code-string-color)">"tag:backend"</span><span>, 
</span><span>        </span><span style="color:var(--code-string-color)">"path:packages/internal/**/*"</span><span>,
</span><span>        </span><span style="color:var(--code-string-color)">"some-specific-workspace"</span><span>,
</span><span>        </span><span style="color:var(--code-string-color)">"not:name:my-negated-name-pattern-*"</span><span>
</span>      ]
<!-- -->    }
<!-- -->  }
<!-- -->});</code></pre>
<p>What makes this powerful beyond having TypeScript autofill for config properties is the ability
to share code between config files via import/export, so you can more easily set up similar
config files between workspaces, using whatever pattern you want in TypeScript.</p>
<p>The above config has some of the newest features showcased. Read on to learn more about them.</p>
<h2 class="anchor anchorTargetStickyNavbar_HeLj" id="workspace-tags">Workspace Tags<a href="https://smorsic.io/blog/bun-workspaces-v1_5#workspace-tags" class="hash-link" aria-label="Direct link to Workspace Tags" title="Direct link to Workspace Tags" translate="no">​</a></h2>
<p>This is a simple concept but potentially very powerful for a project.</p>
<p>Prior, workspaces
had several identifying traits: their name (from their respective <code>package.json</code>), any <a href="https://bunworkspaces.com/concepts/workspace-aliases" target="_blank" rel="noopener noreferrer" class="">aliases</a> configured
in their <code>bun-workspaces</code> <a href="https://bunworkspaces.com/config/workspace" target="_blank" rel="noopener noreferrer" class="">workspace configuration</a>, and their path. It has been
possible to match workspaces by any of these traits via <a href="https://bunworkspaces.com/concepts/workspace-patterns" target="_blank" rel="noopener noreferrer" class="">workspace patterns</a>.</p>
<p>Workspace <strong>tags</strong> are simply strings that can be added to a workspace's <a href="https://bunworkspaces.com/config/workspace" target="_blank" rel="noopener noreferrer" class="">configuration</a> that
can be shared between workspaces.</p>
<p>Where these really come in handy is <a href="https://bunworkspaces.com/concepts/workspace-patterns" target="_blank" rel="noopener noreferrer" class="">workspace patterns</a>, which can be used when
listing scripts and running scripts.</p>
<p>For example, workspace patterns could be <code>workspace-name</code>, <code>path:my-glob/**/*</code>, or now <code>tag:my-tag</code>/<code>tag:my-tag-pattern-*</code>,
the latter matching all workspaces matching the given tag or tag wildcard pattern. In the CLI, <code>bw run lint tag:my-tag</code> will run
the <code>lint</code> script from <code>package.json</code> for all workspaces that have <code>"my-tag"</code> in their configured tags.</p>
<p>These can be a useful pattern to use in workspace dependency rules, a new feature, covered next:</p>
<h2 class="anchor anchorTargetStickyNavbar_HeLj" id="workspace-dependency-rules">Workspace Dependency Rules<a href="https://smorsic.io/blog/bun-workspaces-v1_5#workspace-dependency-rules" class="hash-link" aria-label="Direct link to Workspace Dependency Rules" title="Direct link to Workspace Dependency Rules" translate="no">​</a></h2>
<p>Monorepos are powerful for code sharing. The <code>bun-workspaces</code> documentation covers some of the basics behind <a href="https://bunworkspaces.com/concepts/workspace-dependencies" target="_blank" rel="noopener noreferrer" class="">workspace dependencies</a>,
which are declared in package.json dependencies like <code>"my-workspace-name": "workspace:*"</code>.</p>
<p>Oftentimes, one of the main reservations people have about making code sharing simpler is leaking code that shouldn't be exposed in
the wrong package.</p>
<p>Monorepos are great for developing a full stack application using TypeScript for both
the frontend and backend, but people don't want their backend code being included in their frontend, especially
for a web app where it's very easy to inspect and get code that was accidentally bundled.</p>
<p>Dependency rules let you use <a href="https://bunworkspaces.com/concepts/workspace-patterns" target="_blank" rel="noopener noreferrer" class="">workspace patterns</a> in your
<a href="https://bunworkspaces.com/config/workspace" target="_blank" rel="noopener noreferrer" class="">workspace configuration</a> in order to allow or deny dependencies,
so that <code>bun-workspaces</code> will exit with an error if any workspaces violate their rules.</p>
<p>For example, you could use the new <strong>tags</strong> to tag all your backend workspaces, so that your frontend workspaces
can then deny backend code.</p>
<p>This does take into account the whole dependency tree, such as when a workspace that violates a rule is a dependency of a
dependency.</p>
<p>With the new TypeScript configs as we have seen earlier, we could set up something like this for frontend
workspaces:</p>
<pre class="syntax-highlighter" style="display:block;overflow-x:auto;padding:0.75rem 1rem;color:var(--code-base-color);background:#1c1b1b;border-radius:0.5rem;background-color:var(--code-block-bg);border:1px solid rgba(40, 44, 52, 0.52);line-height:1.4;margin:0.5rem 0"><code class="language-typescript" style="white-space:pre"><span style="color:var(--code-comment-color)">// configHelpers.ts</span><span>
</span>
<span></span><span style="color:var(--code-keyword-color)">import</span><span> { </span><span style="color:var(--code-keyword-color)">type</span><span> WorkspaceConfig } </span><span style="color:var(--code-keyword-color)">from</span><span> </span><span style="color:var(--code-string-color)">"bun-workspaces/config"</span><span>;
</span>  
<span></span><span style="color:var(--code-keyword-color)">export</span><span> </span><span style="color:var(--code-keyword-color)">const</span><span> FRONTEND_WORKSPACE_CONFIG: WorkspaceConfig = {
</span><span>  </span><span style="color:var(--code-attr-color)">tags</span><span>: [</span><span style="color:var(--code-string-color)">"frontend"</span><span>],
</span><span>  </span><span style="color:var(--code-attr-color)">rules</span><span>: {
</span><span>    </span><span style="color:var(--code-attr-color)">workspaceDependencies</span><span>: {
</span><span>      </span><span style="color:var(--code-attr-color)">denyPatterns</span><span>: [</span><span style="color:var(--code-string-color)">"tag:backend"</span><span>]
</span>    }
<!-- -->  }
<!-- -->};
<!-- -->
<span></span><span style="color:var(--code-keyword-color)">export</span><span> </span><span style="color:var(--code-keyword-color)">const</span><span> BACKEND_WORKSPACE_CONFIG: WorkspaceConfig = {
</span><span>  </span><span style="color:var(--code-attr-color)">tags</span><span>: [</span><span style="color:var(--code-string-color)">"backend"</span><span>]
</span>};</code></pre>
<pre class="syntax-highlighter" style="display:block;overflow-x:auto;padding:0.75rem 1rem;color:var(--code-base-color);background:#1c1b1b;border-radius:0.5rem;background-color:var(--code-block-bg);border:1px solid rgba(40, 44, 52, 0.52);line-height:1.4;margin:0.5rem 0"><code class="language-typescript" style="white-space:pre"><span style="color:var(--code-comment-color)">// packages/my-frontend/bw.workspace.ts</span><span>
</span>
<span></span><span style="color:var(--code-keyword-color)">import</span><span> { defineWorkspaceConfig } </span><span style="color:var(--code-keyword-color)">from</span><span> </span><span style="color:var(--code-string-color)">"bun-workspaces/config"</span><span>;
</span><span></span><span style="color:var(--code-keyword-color)">import</span><span> { FRONTEND_WORKSPACE_CONFIG } </span><span style="color:var(--code-keyword-color)">from</span><span> </span><span style="color:var(--code-string-color)">"../../configHelpers"</span><span>;
</span>
<span></span><span style="color:var(--code-keyword-color)">export</span><span> </span><span style="color:var(--code-keyword-color)">default</span><span> defineWorkspaceConfig({
</span><span>  </span><span style="color:var(--code-attr-color)">alias</span><span>: [</span><span style="color:var(--code-string-color)">"my-fe"</span><span>],
</span>  ...FRONTEND_WORKSPACE_CONFIG
<!-- -->});</code></pre>
<pre class="syntax-highlighter" style="display:block;overflow-x:auto;padding:0.75rem 1rem;color:var(--code-base-color);background:#1c1b1b;border-radius:0.5rem;background-color:var(--code-block-bg);border:1px solid rgba(40, 44, 52, 0.52);line-height:1.4;margin:0.5rem 0"><code class="language-typescript" style="white-space:pre"><span style="color:var(--code-comment-color)">// packages/my-backend/bw.workspace.ts</span><span>
</span>
<span></span><span style="color:var(--code-keyword-color)">import</span><span> { defineWorkspaceConfig } </span><span style="color:var(--code-keyword-color)">from</span><span> </span><span style="color:var(--code-string-color)">"bun-workspaces/config"</span><span>;
</span><span></span><span style="color:var(--code-keyword-color)">import</span><span> { BACKEND_WORKSPACE_CONFIG } </span><span style="color:var(--code-keyword-color)">from</span><span> </span><span style="color:var(--code-string-color)">"../../configHelpers"</span><span>;
</span>
<span></span><span style="color:var(--code-keyword-color)">export</span><span> </span><span style="color:var(--code-keyword-color)">default</span><span> defineWorkspaceConfig({
</span><span>  </span><span style="color:var(--code-attr-color)">alias</span><span>: [</span><span style="color:var(--code-string-color)">"my-be"</span><span>],
</span>  ...BACKEND_WORKSPACE_CONFIG
<!-- -->});</code></pre>
<p>Now you can reuse the <code>configHelpers.ts</code> utilities in any frontend-related or backend-related workspace, and if a frontend workspace
declares a backend workspace as a dependency (or any workspace with a backend dependency itself, etc.), you will get an error.</p>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="update">Update<a href="https://smorsic.io/blog/bun-workspaces-v1_5#update" class="hash-link" aria-label="Direct link to Update" title="Direct link to Update" translate="no">​</a></h3>
<p>Version 1.6 introduced <a href="https://bunworkspaces.com/config/workspace-pattern-configs" target="_blank" rel="noopener noreferrer" class="">workspace pattern configs</a> that can
be used in the project root config for bulk DRY workspace configs.</p>
<h2 class="anchor anchorTargetStickyNavbar_HeLj" id="mcp-server">MCP Server<a href="https://smorsic.io/blog/bun-workspaces-v1_5#mcp-server" class="hash-link" aria-label="Direct link to MCP Server" title="Direct link to MCP Server" translate="no">​</a></h2>
<p>See the <a href="https://bunworkspaces.com/ai/mcp" target="_blank" rel="noopener noreferrer" class="">Documentation</a> for information about setup.</p>
<p><code>bun-workspaces</code> is a newer, growing open source tool. That means that AI doesn't generally know about it by
default. This is an issue with new open source, but that doesn't mean modern development
is now stuck with only pre-existing open source tooling.</p>
<p>Now the CLI ships with an <code>mcp-server</code> command that you can use to set up in your tooling
that supports MCP, like <strong>Cursor</strong> or <strong>Claude Code</strong>.</p>
<p>The server provides instructions outlining the overall use of the <code>bun-workspaces</code> CLI
and TypeScript API to your AI, and it includes <strong>tools</strong> for using some of its functionality
to get project metadata and <strong>resources</strong> that allow it to read more complete docs
about all of its features if necessary.</p>
<p>Now your tooling doesn't have to guess how <code>bun-workspaces</code> works!</p>]]></content:encoded>
            <category>Release</category>
            <category>bun-workspaces</category>
        </item>
        <item>
            <title><![CDATA[The Fundamentals Still Haven't Changed: Typewriters to Tokens]]></title>
            <link>https://smorsic.io/blog/typewriters-to-tokens</link>
            <guid>https://smorsic.io/blog/typewriters-to-tokens</guid>
            <pubDate>Tue, 07 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[1956: An MIT laboratory develops a new version of the Whirlwind I for the U.S. Navy,]]></description>
            <content:encoded><![CDATA[<p><strong>1956</strong>: An MIT laboratory develops a new version of the <em>Whirlwind I</em> for the U.S. Navy,
the first computer with a built-in typewriter keyboard for direct input operation, the <em>Friden Flexowriter</em>,
making punch card procedures obsolete.</p>
<p><strong>1978</strong>: The <em>VT100</em> is released, the first terminal with a video display to support the ANSI
codes used today. There are control codes for the keyboard's lights, reminding me of the
the RGBs on my mechanical keyboard that have been a great way to impress women.</p>
<p><strong>1989</strong>: The <strong>bash</strong> terminal shell is released under one of the first open source licenses,
combining many of the best features of existing shells that could control the Unix
operating system. Two years later, Linus Torvalds ports it to Linux.</p>
<p><strong>2005</strong>: Linus Torvalds releases <strong>git</strong>, an improved system of version control and collaboration
for developing a directory of files, though Linus's first commit to its own source code calls it
"the information manager from hell."</p>
<p><strong>2011</strong>: The idea of a "coding bootcamp" is first seen. The current barrier of entry for code self-education
is lower than most people realize. Years later, I eventually attend one of these bootcamps,
where I am first taught to use <strong>bash</strong> and <strong>git</strong> in macOS's terminal
emulator.</p>
<p><strong>2025</strong>: <strong>Claude Code</strong> is released, including the fully terminal-based AI coding
interface used popularly by developers of all experience levels. A year later, I
attend a vibe coding hype presentation of Claude targeting a non-technical audience that
inevitably ends up emphasizing having some familiarity with <strong>bash</strong> and <strong>git</strong>.</p>
<a href="https://smorsic.io/blog/typewriters-to-tokens#historical-sources" style="color:var(--ifm-font-color-secondary);opacity:0.8;font-size:0.9rem"><p>Sources</p></a>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_HeLj" id="what-even-are-the-fundamentals-of-software-engineering">What even are the fundamentals of software engineering?<a href="https://smorsic.io/blog/typewriters-to-tokens#what-even-are-the-fundamentals-of-software-engineering" class="hash-link" aria-label="Direct link to What even are the fundamentals of software engineering?" title="Direct link to What even are the fundamentals of software engineering?" translate="no">​</a></h2>
<p>A pattern we see in software development is the stark contrast
in the increasingly rapid and dramatic changes to how we work, while some
aspects don't seem to ever die.</p>
<p>People hardly seem to agree on which aspects are dead, which are about to die,
which are worth spending time on, and so on.</p>
<p>It doesn't help that platforms where general tech discourse is held, like LinkedIn
and other social media, reward hot takes and bold predictions more than
patient, measured discussion.</p>
<p>When this is the case, it's hard to find agreement on what fundamental skills even
are.</p>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="to-javascript-or-not-to-javascript">To JavaScript, or not to JavaScript<a href="https://smorsic.io/blog/typewriters-to-tokens#to-javascript-or-not-to-javascript" class="hash-link" aria-label="Direct link to To JavaScript, or not to JavaScript" title="Direct link to To JavaScript, or not to JavaScript" translate="no">​</a></h3>
<p>10 years ago, it was very valuable to just be very strong at JavaScript.
However, even then, saying that knowing JavaScript is a truly <em>fundamental</em> skill was arguably
too superficial.</p>
<p>Now, even the idea that knowing JavaScript is important to write JavaScript
is being challenged.</p>
<p>To say that specifically operating the 1978 <em>VT100</em> terminal is a fundamental skill now is
at least obviously ridiculous.</p>
<p>What are we doing today, though? Many of us still decide to emulate the same
experience and even use the latest AI in similarly ANSI-compatible terminal software.</p>
<p>Apparently, something stuck there, and there is something to this beyond just receiving software hand-me-downs.</p>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="the-practice-of-coding-a-7-foot-toddler">The Practice of Coding: A 7-Foot Toddler<a href="https://smorsic.io/blog/typewriters-to-tokens#the-practice-of-coding-a-7-foot-toddler" class="hash-link" aria-label="Direct link to The Practice of Coding: A 7-Foot Toddler" title="Direct link to The Practice of Coding: A 7-Foot Toddler" translate="no">​</a></h3>
<p>Part of what I believe is difficult about these discussions is the fact that computer science
and coding are very young practices in terms of human history.</p>
<p>While we advance
technology at never-before-seen rates, we barely have time to process what we've
really been doing.</p>
<p>It seems like just as we start to really define development,
something comes along that shakes up the details of that narrative, and we're
left picking up the pieces to see which are still useful to us, some wanting
to throw all those pieces straight into the trash, as if due to personal vendetta.</p>
<div class="center"><img src="https://smorsic.io/blog/images/frank-js-trash.jpeg" height="200px"></div>
<h2 class="anchor anchorTargetStickyNavbar_HeLj" id="an-understood-domain-of-code-music">An Understood Domain of Code: Music<a href="https://smorsic.io/blog/typewriters-to-tokens#an-understood-domain-of-code-music" class="hash-link" aria-label="Direct link to An Understood Domain of Code: Music" title="Direct link to An Understood Domain of Code: Music" translate="no">​</a></h2>
<p>Something many academic musicians are well aware of is the fact that our primary means
of communicating about music in great detail (sheet music, musical theory concepts,
acoustic science, etc.) is a form
of musical <strong>code</strong>.</p>
<p>One could argue that sheet music is one of the oldest and most sophisticated
forms of written code, early forms going back to <strong>2000 B.C.</strong></p>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="an-efficient-code">An Efficient Code<a href="https://smorsic.io/blog/typewriters-to-tokens#an-efficient-code" class="hash-link" aria-label="Direct link to An Efficient Code" title="Direct link to An Efficient Code" translate="no">​</a></h3>
<p>Sheet music is extremely efficient at representing a
large amount of music.</p>
<p>One page potentially equates to several minutes
worth of music, providing a large variety of instructions for the performer: pitch,
duration, key, tempo, volume, harmonic context, rhythmic meter, technique, emotion,
free-form comments, and more.</p>
<p>These aspects are all able to be compressed to a relatively
short line that's still readable enough for a skilled reader to digest it at sight.
Is any of this sounding familiar?</p>
<div class="center"><img src="https://smorsic.io/blog/images/sheet-music-cantabile.jpeg"></div>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="known-musical-fundamentals">Known Musical Fundamentals<a href="https://smorsic.io/blog/typewriters-to-tokens#known-musical-fundamentals" class="hash-link" aria-label="Direct link to Known Musical Fundamentals" title="Direct link to Known Musical Fundamentals" translate="no">​</a></h3>
<p>People have been practicing and teaching music for millennia. Thanks to this,
a lot is known about the mental and physical development
of musical skills. There are exercises and whole thought systems passed down for generations
broadly.</p>
<p>That being said, on some level, musical traditions still haven't escaped the bulldozer of
tech.</p>
<p>A guitarist can use an electronic tuner instead of their ear. However,
most musicians still would agree that <strong>this in no way makes hearing when
something is out-of-tune obsolete</strong>, even for guitarists.</p>
<p>An electronic musician can fully automate rhythm. However, again, most musicians
agree that <strong>this does not make understanding rhythm obsolete</strong>. Many electronic
musicians eventually come to value having physical electronic
instruments like beat pads so they can capture rhythm and emphasis from their
head naturally, using old school tools: their minds and their hands.</p>
<h2 class="anchor anchorTargetStickyNavbar_HeLj" id="the-nature-of-fundamentals-in-a-sense">The Nature of Fundamentals, in a Sense<a href="https://smorsic.io/blog/typewriters-to-tokens#the-nature-of-fundamentals-in-a-sense" class="hash-link" aria-label="Direct link to The Nature of Fundamentals, in a Sense" title="Direct link to The Nature of Fundamentals, in a Sense" translate="no">​</a></h2>
<p>The two examples I provided hit two major fundamentals for a musician: A <strong>sense of
pitch</strong>, and a <strong>sense of rhythm</strong>.</p>
<p>Fundamental skills are often related to some underlying <strong>human sense</strong> that is
applicable regardless of how advanced the technology used on top of it is.</p>
<div class="center"><img src="https://smorsic.io/blog/images/i-sense-it.jpeg" height="200px"></div>
<h2 class="anchor anchorTargetStickyNavbar_HeLj" id="engineering-senses">Engineering Senses<a href="https://smorsic.io/blog/typewriters-to-tokens#engineering-senses" class="hash-link" aria-label="Direct link to Engineering Senses" title="Direct link to Engineering Senses" translate="no">​</a></h2>
<p>It's difficult to truly capture the internal senses we're using as software
engineers. Many that can feel it have been trying to express it in a variety of
ways despite the difficulty, including myself.</p>
<p>I'm going to attempt here to describe some of the "senses" that engineers use
that are still fundamental today. I won't pretend that it's an exhaustive list.</p>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="eye-rolling-acronyms">Eye-Rolling Acronyms<a href="https://smorsic.io/blog/typewriters-to-tokens#eye-rolling-acronyms" class="hash-link" aria-label="Direct link to Eye-Rolling Acronyms" title="Direct link to Eye-Rolling Acronyms" translate="no">​</a></h3>
<p>DRY. SOLID. YAGNI. Some of these acronyms for engineering practices and principles
are practiced dogmatically or dismissed entirely. Dismissal is becoming
more prevalent these days.</p>
<p>I think many of these principles are still relevant and beneficial,
even if one is heavily relying on AI generation, and these spur
from our fundamental engineering senses.</p>
<h2 class="anchor anchorTargetStickyNavbar_HeLj" id="a-sense-of-organization">A Sense of Organization<a href="https://smorsic.io/blog/typewriters-to-tokens#a-sense-of-organization" class="hash-link" aria-label="Direct link to A Sense of Organization" title="Direct link to A Sense of Organization" translate="no">​</a></h2>
<p>Even if you aren't experienced as a coder, you have some sense of when
you are poorly organized, physically or digitally. We learn to organize
a room earlier in life on some level, and we also tend to learn lessons
with digital organization when we lose or confuse important files for school or
work.</p>
<p>Many of the classical engineering principles could be said
to be practices of organization.</p>
<p>DRY (Don't Repeat Yourself) and the S in SOLID (Single Responsibility)
are a couple of these, and they're closely related.</p>
<p>The DRY idea isn't and has never
meant be "Any repetition is automatically bad," and Single Responsibility doesn't
mean "Nothing can ever do more than one thing."</p>
<p>The real spirit of DRY is "Code becomes hard to keep track of when too many parts
do the same thing," and an aspect of Single Responsibility is "When something has
too different of jobs, it's hard to organize it properly."</p>
<p>If you have three copies of an essay for school in different folders on your computer,
and you edit one on your USB drive, unplug it, forget you edited it there, then edit
the copy in your Documents folder, save it, and then turn in a different copy in your
Downloads that appeared when it was time to upload it, you already suffered from a lack of DRY.</p>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="organization-and-ai">Organization and AI<a href="https://smorsic.io/blog/typewriters-to-tokens#organization-and-ai" class="hash-link" aria-label="Direct link to Organization and AI" title="Direct link to Organization and AI" translate="no">​</a></h3>
<p>When I engage with AI, I keep these principles in mind. I don't let it repeat
code too much, because I see there's a good chance that it misses the fact there's repetition later.
That chance grows as the code grows. This coincidentally is the same thing that happens to
people who are working with code. I tend to refactor repeated code at the same timing
that I would want if I was writing without any AI.</p>
<p>YAGNI (you aren't gonna need it) is a principle that counterbalances over-engineering
by discouraging adding unnecessary features or designing features for complexity beyond
what is practical. When one can't define a clear scope or purpose
for AI's work, it can generate a lot of unnecessary code. That code then will get in its
own way later, the same way it does for people. Having precise intent comes from
engineering experience that can recognize when a feature is overkill.</p>
<p>An AI tool only works with so much context,
just like us, and it never has a perfect memory or history of every engineering decision
that you or it made. To me, it has appeared that no matter how smart any AI tool
I've used gets, it benefits from the same things humans benefit from when it comes
to code organization, and I have not yet experienced a lack of need for this organization
to be driven by my own senses and am skeptical about that fundamentally changing.</p>
<h2 class="anchor anchorTargetStickyNavbar_HeLj" id="a-sense-of-clarity-in-naming">A Sense of Clarity in Naming<a href="https://smorsic.io/blog/typewriters-to-tokens#a-sense-of-clarity-in-naming" class="hash-link" aria-label="Direct link to A Sense of Clarity in Naming" title="Direct link to A Sense of Clarity in Naming" translate="no">​</a></h2>
<p>Naming has long been said to be one of the most difficult problems in coding.</p>
<p>This is because a good name succinctly describes what something is, but it strips
away a massive amount of detail, only allowing a relatively short sequence of text characters.</p>
<p>If it's too vague, every time you see it, there are too many different things it could be.
If it's more specific than what it really is, then it subtly hides aspects of itself.
If it's inaccurate, it's inherently confusing.</p>
<p>You've probably experienced issues with naming in other areas of life.
I once lived in a city where the electric company was called CPS, who
show up on your doorstep with hats and name tags bearing this. It's not the best
practice to use a name that is very easily confused with something of the same name
without anything extra to qualify it.
(Ever deal with the word "client" on its own in a coding context?)</p>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="naming-and-ai">Naming and AI<a href="https://smorsic.io/blog/typewriters-to-tokens#naming-and-ai" class="hash-link" aria-label="Direct link to Naming and AI" title="Direct link to Naming and AI" translate="no">​</a></h3>
<p>This matters whether an AI or a human is reading code. This applies when prompting
an AI as well, the same as it would confuse someone if you said "I want you to work
on my project called My Cool API" that is actually a desktop GUI.</p>
<p>We are not at a point where 100% of code can be trusted blindly to AI, and I'm not going to
try to predict a day where that becomes true. I also find it hard to believe that's coming
as soon as some people say and in the form they describe it, and there's always the chance
we're not really going to get there, just like how ANSI terminals still have not died for around 50 years.
Regardless, right now and for probably longer than the biggest hype-drivers say, we're not there.</p>
<p>I had to code and debug <code>bun-workspaces</code>'s fancy terminal output
when running scripts in parallel mostly myself, because AI is not well-trained on making fancy terminal
interfaces, and I found that any model I used was pretty terrible at debugging it,
leaving most the real work to me.</p>
<p>This is an extreme example, but I often find that there are important features to write
where I at least start them myself or work in hybrid with AI. That means that the names
I decide really matter, because they signal intent to the AI like prompts themselves, again in
the same way good names help humans understand and build on top of existing code, and the human
element is far from irrelevant.</p>
<p>AI also uses a mix of the conventions it's been trained on. Without guiding it with naming conventions
of my own, it will use a mix of them. That makes it difficult for me to debug and
difficult for it to decide on its own patterns going forward. Naming consistency
means that it has less friction on deciding names for itself and understanding
existing names based on a pattern, which are, again, the same benefits people get.</p>
<h2 class="anchor anchorTargetStickyNavbar_HeLj" id="a-sense-of-fluency">A Sense of Fluency<a href="https://smorsic.io/blog/typewriters-to-tokens#a-sense-of-fluency" class="hash-link" aria-label="Direct link to A Sense of Fluency" title="Direct link to A Sense of Fluency" translate="no">​</a></h2>
<p>Coding, music, writing: All of these are essentially
language skills.</p>
<p>After all, you could say language is a code rather than code is
language, when talking about the general idea of code being simply
a system of symbolic representation.</p>
<p>There have been studies like <a href="https://www.washington.edu/news/2020/03/02/not-a-math-person-you-may-be-better-at-learning-to-code-than-you-think/" target="_blank" rel="noopener noreferrer" class="">this one</a>
that have shown that language skills predict coding skills better
than math skills.</p>
<p>If you've studied another language at all, you know you can feel
a visceral difference between reading your main language and one
you aren't as fluent in.</p>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="fluency-and-ai">Fluency and AI<a href="https://smorsic.io/blog/typewriters-to-tokens#fluency-and-ai" class="hash-link" aria-label="Direct link to Fluency and AI" title="Direct link to Fluency and AI" translate="no">​</a></h3>
<p>At the moment, we're seeing that experienced engineers are the ones
often benefitting from new AI development workflows the most.</p>
<p>If this is true, then the argument that fluency in code is no longer
needed is backwards. The implication is that having what
is sometimes being posited as "traditional" coding fluency is still a valuable
thing to learn.</p>
<p>Before AI, I had already been engaging in code reviews and managing
several growing projects at a time. Fluency was imperative because
no one has time to deeply study the code they've already written or others
wrote from the ground up constantly.</p>
<p>Now, I still need this fluency, maybe more than ever. Sometimes I focus it on tests. If AI
creates a helper module for me, I want to be able to skim it and
understand how it's working. If that module is too complex to fully process in my head,
then that's where having tests, code comprised mostly of obvious "when this
happens, then this should happen" statements using straightforward syntax
becomes even more valuable. This then gives me a place in the code that is easier to
read quickly with confidence to know that my project is still in good shape.</p>
<h2 class="anchor anchorTargetStickyNavbar_HeLj" id="conclusion">Conclusion<a href="https://smorsic.io/blog/typewriters-to-tokens#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" translate="no">​</a></h2>
<p>I'm in no way trying to gatekeep people from coding with AI. I'm not saying you need to stop
everything you're doing just to work on these fundamentals before you "earn" it. But I am saying that I
strongly do not believe we're at a point where you can ignore them entirely,
and because of the nature of fundamentals themselves, I find it hard to believe
that they will actually ever truly go away without just taking some new form.</p>
<p>Fundamentally, <code>git</code> isn't complex because of the complexity of code but due to
the complexity of versioning and collaborating with <strong>text</strong>-based projects. Text
doesn't die because it's an extremely efficient, established means of communication that works well
for both machines and people, which is part of why modern engineers are still are using software
that looks and acts something like a virtualized 1978 computer console, because
text input and text output have been and still remain king, whether it's terminal stdio,
AI prompting, or sending stupid messages to your loved ones.</p>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="historical-sources">Historical Sources<a href="https://smorsic.io/blog/typewriters-to-tokens#historical-sources" class="hash-link" aria-label="Direct link to Historical Sources" title="Direct link to Historical Sources" translate="no">​</a></h3>
<ul>
<li class=""><a href="https://www.computerhistory.org/timeline/1956/" target="_blank" rel="noopener noreferrer" class="">Computer History Timeline</a></li>
<li class=""><a href="https://en.wikipedia.org/wiki/Whirlwind_I" target="_blank" rel="noopener noreferrer" class="">The Whirlwind I</a></li>
<li class=""><a href="https://en.wikipedia.org/wiki/Friden_Flexowriter" target="_blank" rel="noopener noreferrer" class="">The Flexowriter</a></li>
<li class=""><a href="https://en.wikipedia.org/wiki/VT100" target="_blank" rel="noopener noreferrer" class="">The VT100</a></li>
<li class="">
<a href="https://en.wikipedia.org/wiki/Bash_(Unix_shell)">Bash's history</a>
</li>
<li class=""><a href="https://en.wikipedia.org/wiki/Sheet_music" target="_blank" rel="noopener noreferrer" class="">Sheet music's history</a></li>
</ul>]]></content:encoded>
            <category>Education</category>
            <category>Opinion</category>
            <category>AI</category>
        </item>
        <item>
            <title><![CDATA[bun-workspaces: Version 1 is here! - Package Overview]]></title>
            <link>https://smorsic.io/blog/bun-workspaces-v1</link>
            <guid>https://smorsic.io/blog/bun-workspaces-v1</guid>
            <pubDate>Sat, 04 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[⚠️ UPDATE: bun-workspaces is now developed as]]></description>
            <content:encoded><![CDATA[<p><b>⚠️ UPDATE:</b> <code>bun-workspaces</code> is now developed as
<a href="https://pacwich.dev/" target="_blank" rel="noopener noreferrer" class=""><code>pacwich</code></a>, which supports Bun, npm, and pnpm workspaces.</p>
<p><code>bun-workspaces</code> was in alpha for several months, and now version 1 is here! The package is officially at <code>1.1.2</code> at the time of writing.</p>
<p>Along with this first stable version is the first blog post! Let's take a walk through some of the core features of <code>bun-workspaces</code>, both its CLI and its TypeScript API.</p>
<!-- -->
<div class="bwunsters_c0EE"><img src="https://smorsic.io/blog/images/bwaby_20x22.png" class="pixel-art-image  undefined"><img src="https://smorsic.io/blog/images/bwunsterito_34x38.png" class="pixel-art-image  undefined"><img src="https://smorsic.io/blog/images/bwunster_64x70.png" class="pixel-art-image small undefined"></div>
<h2 class="anchor anchorTargetStickyNavbar_HeLj" id="background">Background<a href="https://smorsic.io/blog/bun-workspaces-v1#background" class="hash-link" aria-label="Direct link to Background" title="Direct link to Background" translate="no">​</a></h2>
<p>Before diving directly into features, I'd like to give some background to the package first. You can <a href="https://smorsic.io/blog/bun-workspaces-v1#the-features" class="">skip forward</a>
to get right to how <code>bun-workspaces</code> works.</p>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="fundamentals">Fundamentals<a href="https://smorsic.io/blog/bun-workspaces-v1#fundamentals" class="hash-link" aria-label="Direct link to Fundamentals" title="Direct link to Fundamentals" translate="no">​</a></h3>
<p>It's good to familiarize yourself with the basics of Bun's <a href="https://bun.com/docs/pm/workspaces" target="_blank" rel="noopener noreferrer" class="">workspaces</a> features.
If you're coming from Node, Bun workspaces are extremely similar to <a href="https://docs.npmjs.com/cli/v7/using-npm/workspaces?v=true" target="_blank" rel="noopener noreferrer" class="">npm workspaces</a>.</p>
<p>The general idea is not very complicated: Normally you have one package in a git repo, defined by a <code>package.json</code> at the root. When using workspaces,
you can nest any number of <code>package.json</code>s in subdirectories in your project, with configuration for this in your root <code>package.json</code>.</p>
<p>These workspaces can import and export code between each other like local modules that are named by their <code>package.json</code>, acting like local node_modules.
I have more about this in the <a href="https://bunworkspaces.com/concepts/workspace-dependencies" target="_blank" rel="noopener noreferrer" class="">documentation</a>.</p>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="history">History<a href="https://smorsic.io/blog/bun-workspaces-v1#history" class="hash-link" aria-label="Direct link to History" title="Direct link to History" translate="no">​</a></h3>
<h4 class="anchor anchorTargetStickyNavbar_HeLj" id="inspiration">Inspiration<a href="https://smorsic.io/blog/bun-workspaces-v1#inspiration" class="hash-link" aria-label="Direct link to Inspiration" title="Direct link to Inspiration" translate="no">​</a></h4>
<p>The inspiration for <code>bun-workspaces</code> comes from my experience working with TypeScript and monorepos in general.
I've worked in setups where I was publishing private npm packages when I wished I had a monorepo,
and I've worked in complex monorepo setups, such as managing microfrontends with Nx.</p>
<p>The bare-bones monorepo features of Node and Bun don't offer a lot in terms of features, but
the alternatives are generally heavyweight frameworks that require a steep learning curve and complex configuration, like Nx.</p>
<p>For years, I've wanted to meet in the middle and have a monorepo power user layer that works in tandem with native workspaces, but
I didn't get around to it while working previous jobs.</p>
<h4 class="anchor anchorTargetStickyNavbar_HeLj" id="how-it-started">How It Started<a href="https://smorsic.io/blog/bun-workspaces-v1#how-it-started" class="hash-link" aria-label="Direct link to How It Started" title="Direct link to How It Started" translate="no">​</a></h4>
<p>I originally created <code>bun-workspaces</code> to finally start
a project that works with native workspaces, but it wasn't very serious and was mostly a workaround for a now irrelevant limitation of Bun's <code>--filter</code> feature
at first.</p>
<p>But, I noticed that several developers liked the idea. The positive reactions I've received and the
usefulness I've found for myself have now propelled the project forward from a tiny, slapped-together package with a basic CLI and README
to the project you see now that has a well-tested CLI and TypeScript library, a full documentation website with an <a href="https://bunworkspaces.com/web-cli" target="_blank" rel="noopener noreferrer" class="">in-browser
demo</a> of the CLI, this blog, and more.</p>
<h4 class="anchor anchorTargetStickyNavbar_HeLj" id="going-forward">Going Forward<a href="https://smorsic.io/blog/bun-workspaces-v1#going-forward" class="hash-link" aria-label="Direct link to Going Forward" title="Direct link to Going Forward" translate="no">​</a></h4>
<p>I am just as much of a user or more than any of my
current users, so I'm driven to keep adding functionality, with the hypothesis that a lot of monorepo frameworks' features
can be achieved in a more lightweight package that works with, rather than against, native tooling.</p>
<p>I don't think well-automated monorepos are just for large enterprise projects, so I want there to be tooling that gives more accessible power to
users that are solo, in smaller teams, or simply have projects that aren't worth a heavy framework.</p>
<p>You can see a more detailed roadmap <a href="https://bunworkspaces.com/roadmap" target="_blank" rel="noopener noreferrer" class="">here</a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_HeLj" id="the-features">The Features<a href="https://smorsic.io/blog/bun-workspaces-v1#the-features" class="hash-link" aria-label="Direct link to The Features" title="Direct link to The Features" translate="no">​</a></h2>
<p>The main <a href="https://bunworkspaces.com/" target="_blank" rel="noopener noreferrer" class="">documentation</a> intends to provide the true exhaustive description of <code>bun-workspaces</code>'s features,
so I won't repeat everything here, but I would like to highlight some core features.</p>
<p>In general, you can say <code>bun-workspaces</code> has two main jobs:</p>
<ul>
<li class="">Getting metadata about a monorepo's workspace structure</li>
<li class="">Running scripts across workspaces with advanced control, in parallel (by default) or sequentially</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="a-cli-and-api-in-parity">A CLI and API in Parity<a href="https://smorsic.io/blog/bun-workspaces-v1#a-cli-and-api-in-parity" class="hash-link" aria-label="Direct link to A CLI and API in Parity" title="Direct link to A CLI and API in Parity" translate="no">​</a></h3>
<p>This package started out as only a CLI. The idea of shipping a TypeScript library (the API)
along with the CLI sounded like a lot of extra work at first.</p>
<p>However, I had already designed CLI features to have strong CLI-agnostic code behind them, a good practice
for any kind of interface, so I already almost had a TypeScript library.
Making it public just meant taking extra care to its design and ensuring that it's documented.</p>
<h5 class="anchor anchorTargetStickyNavbar_HeLj" id="benefits">Benefits<a href="https://smorsic.io/blog/bun-workspaces-v1#benefits" class="hash-link" aria-label="Direct link to Benefits" title="Direct link to Benefits" translate="no">​</a></h5>
<p>This has become a great boon to the project, as it forces the core features to be designed strongly before thinking
about the CLI, encourages another strong layer of testing to diagnose core logic issues separately from CLI issues, and allows users to
choose either shell scripting or JavaScript/TypeScript scripting with first-class support to accomplish the same thing.</p>
<h4 class="anchor anchorTargetStickyNavbar_HeLj" id="comparison">Comparison<a href="https://smorsic.io/blog/bun-workspaces-v1#comparison" class="hash-link" aria-label="Direct link to Comparison" title="Direct link to Comparison" translate="no">​</a></h4>
<p>We can directly look at a shell script and a TypeScript script that perform essentially the same functions below:</p>
<h5 class="anchor anchorTargetStickyNavbar_HeLj" id="cli">CLI<a href="https://smorsic.io/blog/bun-workspaces-v1#cli" class="hash-link" aria-label="Direct link to CLI" title="Direct link to CLI" translate="no">​</a></h5>
<pre class="syntax-highlighter" style="display:block;overflow-x:auto;padding:0.75rem 1rem;color:var(--code-base-color);background:#1c1b1b;border-radius:0.5rem;background-color:var(--code-block-bg);border:1px solid rgba(40, 44, 52, 0.52);line-height:1.4;margin:0.5rem 0"><code class="language-bash" style="white-space:pre"><span style="color:var(--code-comment-color)"># A shell alias for simplicity</span><span>
</span><span></span><span style="color:var(--code-built-in-color)">alias</span><span> bw=</span><span style="color:var(--code-string-color)">"bunx bun-workspaces"</span><span>
</span>
<span></span><span style="color:var(--code-comment-color)"># List workspaces</span><span>
</span>bw ls
<!-- -->
<span></span><span style="color:var(--code-comment-color)"># Get info about my-workspace in JSON format</span><span>
</span>bw info my-workspace --json --pretty
<!-- -->
<span></span><span style="color:var(--code-comment-color)"># Get same info using a configured alias (optional bw.workspace.json)</span><span>
</span>bw info my-alias --json --pretty
<!-- -->
<span></span><span style="color:var(--code-comment-color)"># Run the "lint" script in parallel across all workspaces</span><span>
</span><span></span><span style="color:var(--code-comment-color)"># and write a report to results.json</span><span>
</span>bw run lint --json-outfile=results.json</code></pre>
<h5 class="anchor anchorTargetStickyNavbar_HeLj" id="api">API<a href="https://smorsic.io/blog/bun-workspaces-v1#api" class="hash-link" aria-label="Direct link to API" title="Direct link to API" translate="no">​</a></h5>
<pre class="syntax-highlighter" style="display:block;overflow-x:auto;padding:0.75rem 1rem;color:var(--code-base-color);background:#1c1b1b;border-radius:0.5rem;background-color:var(--code-block-bg);border:1px solid rgba(40, 44, 52, 0.52);line-height:1.4;margin:0.5rem 0"><code class="language-typescript" style="white-space:pre"><span style="color:var(--code-keyword-color)">import</span><span> { createFileSystemProject } </span><span style="color:var(--code-keyword-color)">from</span><span> </span><span style="color:var(--code-string-color)">"bun-workspaces"</span><span>;
</span>
<span></span><span style="color:var(--code-comment-color)">// Initialize Project, by default at process.cwd()</span><span>
</span><span></span><span style="color:var(--code-keyword-color)">const</span><span> myProject = createFileSystemProject();
</span>
<span></span><span style="color:var(--code-comment-color)">// List of Workspaces</span><span>
</span><span></span><span style="color:var(--code-keyword-color)">const</span><span> myWorkspaces = myProject.workspaces;
</span>
<span></span><span style="color:var(--code-comment-color)">// Workspace aliases are validated to be unique from workspace names,</span><span>
</span><span></span><span style="color:var(--code-comment-color)">// so this is unambiguous</span><span>
</span><span></span><span style="color:var(--code-keyword-color)">const</span><span> myWorkspace = myProject.findWorkspaceByNameOrAlias(</span><span style="color:var(--code-string-color)">"my-alias"</span><span>);
</span>
<span></span><span style="color:var(--code-comment-color)">// Run the lint script in parallel across workspaces</span><span>
</span><span></span><span style="color:var(--code-keyword-color)">const</span><span> { output, summary } = myProject.runScriptAcrossWorkspaces({
</span><span>  </span><span style="color:var(--code-attr-color)">script</span><span>: </span><span style="color:var(--code-string-color)">"lint"</span><span>,
</span>});
<!-- -->
<span></span><span style="color:var(--code-keyword-color)">for</span><span> </span><span class="hljs-function" style="color:#f08d49">await</span><span class="hljs-function">(</span><span class="hljs-function hljs-params" style="color:var(--code-keyword-color)">const</span><span class="hljs-function hljs-params"> { chunk, metadata } </span><span class="hljs-function hljs-params" style="color:var(--code-keyword-color)">of</span><span class="hljs-function hljs-params"> output.text()</span><span class="hljs-function">)</span><span>{
</span><span>  </span><span style="color:var(--code-comment-color)">// console.log(chunk); // the output chunk's content (string)</span><span>
</span><span>  </span><span style="color:var(--code-comment-color)">// console.log(metadata.streamName); // "stdout" or "stderr"</span><span>
</span><span>  </span><span style="color:var(--code-comment-color)">// console.log(metadata.workspace); // the Workspace the output is from</span><span>
</span>}
<!-- -->
<span></span><span style="color:var(--code-comment-color)">// Object with details about exit results etc.</span><span>
</span><span></span><span style="color:var(--code-comment-color)">// This is the same data as the CLI's --json-outfile</span><span>
</span><span></span><span style="color:var(--code-keyword-color)">const</span><span> results = </span><span style="color:var(--code-keyword-color)">await</span><span> summary;</span></code></pre>
<p>See the <a href="https://bunworkspaces.com/cli" target="_blank" rel="noopener noreferrer" class="">full CLI docs</a> and <a href="https://bunworkspaces.com/api" target="_blank" rel="noopener noreferrer" class="">full API
docs</a> for more.</p>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="aliases-and-configuration-files">Aliases and Configuration Files<a href="https://smorsic.io/blog/bun-workspaces-v1#aliases-and-configuration-files" class="hash-link" aria-label="Direct link to Aliases and Configuration Files" title="Direct link to Aliases and Configuration Files" translate="no">​</a></h3>
<p>Part of <code>bun-workspaces</code>'s philosophy is to never require special configuration, since it
can determine a project's structure simply from metadata available from Bun.</p>
<p>That being said, optional config can be used, such as to define aliases for workspaces,
which can make it easier to reference a workspace with a long <code>package.json</code> name such as
<code>@my-organization/my-long-package-name</code>.</p>
<p>These aliases can't clash with workspace names so that they can also unambiguously represent a workspace.</p>
<p>An example configuration file that might be at <code>my-project/my-workspaces/bw.workspace.json</code>:</p>
<pre class="syntax-highlighter" style="display:block;overflow-x:auto;padding:0.75rem 1rem;color:var(--code-base-color);background:#1c1b1b;border-radius:0.5rem;background-color:var(--code-block-bg);border:1px solid rgba(40, 44, 52, 0.52);line-height:1.4;margin:0.5rem 0"><code class="language-json" style="white-space:pre"><span>{
</span><span>  </span><span style="color:var(--code-attr-color)">"alias"</span><span>: </span><span style="color:var(--code-string-color)">"my-alias"</span><span>
</span>}</code></pre>
<p>You can read more about configuration <a href="https://bunworkspaces.com/config" target="_blank" rel="noopener noreferrer" class="">here</a>.</p>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="workspace-patterns">Workspace Patterns<a href="https://smorsic.io/blog/bun-workspaces-v1#workspace-patterns" class="hash-link" aria-label="Direct link to Workspace Patterns" title="Direct link to Workspace Patterns" translate="no">​</a></h3>
<p>Workspaces can be filtered or selected for certain features using workspace patterns. You can read more about these
<a href="https://bunworkspaces.com/concepts/workspace-patterns" target="_blank" rel="noopener noreferrer" class="">here</a>.</p>
<h6 class="anchor anchorTargetStickyNavbar_HeLj" id="cli-1">CLI<a href="https://smorsic.io/blog/bun-workspaces-v1#cli-1" class="hash-link" aria-label="Direct link to CLI" title="Direct link to CLI" translate="no">​</a></h6>
<pre class="syntax-highlighter" style="display:block;overflow-x:auto;padding:0.75rem 1rem;color:var(--code-base-color);background:#1c1b1b;border-radius:0.5rem;background-color:var(--code-block-bg);border:1px solid rgba(40, 44, 52, 0.52);line-height:1.4;margin:0.5rem 0"><code class="language-bash" style="white-space:pre"><span style="color:var(--code-comment-color)"># Filter the workspace list by patterns.</span><span>
</span><span></span><span style="color:var(--code-comment-color)"># This could be used to test patterns</span><span>
</span><span></span><span style="color:var(--code-comment-color)"># before using the run command.</span><span>
</span><span></span><span style="color:var(--code-comment-color)">#</span><span>
</span><span></span><span style="color:var(--code-comment-color)"># my-workspace may match a name or alias,</span><span>
</span><span></span><span style="color:var(--code-comment-color)"># while "my-pattern-*" matches by name pattern only.</span><span>
</span><span></span><span style="color:var(--code-comment-color)">#</span><span>
</span><span></span><span style="color:var(--code-comment-color)"># The "path:" specifier matches workspace directories</span><span>
</span><span></span><span style="color:var(--code-comment-color)"># relative to the project root via glob </span><span>
</span><span>bw ls my-workspace </span><span style="color:var(--code-string-color)">"my-pattern-*"</span><span> </span><span style="color:var(--code-string-color)">"path:my-glob/**/*"</span><span>
</span>
<span></span><span style="color:var(--code-comment-color)"># Run for workspaces that have my-script and match the patterns.</span><span>
</span><span></span><span style="color:var(--code-comment-color)"># You can see that it's still possible to match aliases by wildcard</span><span>
</span><span>bw run my-script </span><span style="color:var(--code-string-color)">"alias:my-alias-*"</span><span> </span><span style="color:var(--code-string-color)">"path:my-glob/**/*"</span><span>
</span>
<span></span><span style="color:var(--code-comment-color)"># In general, any positional arguments for commands have </span><span>
</span><span></span><span style="color:var(--code-comment-color)"># alternative flag options</span><span>
</span>bw run \
<span>  --workspace-patterns=</span><span style="color:var(--code-string-color)">"my-workspace path:my-glob/**/*"</span><span> \
</span><span>  --script=</span><span style="color:var(--code-string-color)">"my-script"</span></code></pre>
<h6 class="anchor anchorTargetStickyNavbar_HeLj" id="api-1">API<a href="https://smorsic.io/blog/bun-workspaces-v1#api-1" class="hash-link" aria-label="Direct link to API" title="Direct link to API" translate="no">​</a></h6>
<pre class="syntax-highlighter" style="display:block;overflow-x:auto;padding:0.75rem 1rem;color:var(--code-base-color);background:#1c1b1b;border-radius:0.5rem;background-color:var(--code-block-bg);border:1px solid rgba(40, 44, 52, 0.52);line-height:1.4;margin:0.5rem 0"><code class="language-typescript" style="white-space:pre"><span style="color:var(--code-comment-color)">// Similar to listing by pattern via the CLI</span><span>
</span><span></span><span style="color:var(--code-keyword-color)">const</span><span> myWorkspaces = myProject.findWorkspacesByPattern(
</span><span>  </span><span style="color:var(--code-string-color)">"my-workspace"</span><span>,
</span><span>  </span><span style="color:var(--code-string-color)">"my-pattern-*"</span><span>,
</span><span>  </span><span style="color:var(--code-string-color)">"path:my-glob/**/*"</span><span>
</span>);
<!-- -->
<span></span><span style="color:var(--code-comment-color)">// Same as the above CLI run command</span><span>
</span><span></span><span style="color:var(--code-keyword-color)">const</span><span> { output, summary } = myProject.runScriptAcrossWorkspaces({
</span><span>  </span><span style="color:var(--code-attr-color)">workspacePatterns</span><span>: [
</span><span>    </span><span style="color:var(--code-string-color)">"alias:my-alias-*"</span><span>,
</span><span>    </span><span style="color:var(--code-string-color)">"path:my-glob/**/*"</span><span>
</span>  ],
<span>  </span><span style="color:var(--code-attr-color)">script</span><span>: </span><span style="color:var(--code-string-color)">"my-script"</span><span>,
</span>});</code></pre>
<h3 class="anchor anchorTargetStickyNavbar_HeLj" id="inline-scripts-via-the-bun-shell">Inline Scripts via the Bun Shell<a href="https://smorsic.io/blog/bun-workspaces-v1#inline-scripts-via-the-bun-shell" class="hash-link" aria-label="Direct link to Inline Scripts via the Bun Shell" title="Direct link to Inline Scripts via the Bun Shell" translate="no">​</a></h3>
<p>Running scripts in <code>bun-workspaces</code> usually means running a command from the "scripts" in workspaces' respective <code>package.json</code>s.</p>
<p>However, it's possible to also write "inline scripts," which are simply plain shell commands.</p>
<p>Bun created the <a href="https://bun.com/docs/runtime/shell" target="_blank" rel="noopener noreferrer" class="">Bun Shell</a> as a cross-platform bash-like shell,
and this turned about to be perfect for this feature.</p>
<p>The following code uses the Bun Shell to run the inline command in all workspaces, so its behavior should
be consistent across all major operating systems:</p>
<h6 class="anchor anchorTargetStickyNavbar_HeLj" id="cli-2">CLI<a href="https://smorsic.io/blog/bun-workspaces-v1#cli-2" class="hash-link" aria-label="Direct link to CLI" title="Direct link to CLI" translate="no">​</a></h6>
<pre class="syntax-highlighter" style="display:block;overflow-x:auto;padding:0.75rem 1rem;color:var(--code-base-color);background:#1c1b1b;border-radius:0.5rem;background-color:var(--code-block-bg);border:1px solid rgba(40, 44, 52, 0.52);line-height:1.4;margin:0.5rem 0"><code class="language-bash" style="white-space:pre"><span>bw run </span><span style="color:var(--code-string-color)">"echo 'This is my inline script'"</span><span> --inline</span></code></pre>
<h6 class="anchor anchorTargetStickyNavbar_HeLj" id="api-2">API<a href="https://smorsic.io/blog/bun-workspaces-v1#api-2" class="hash-link" aria-label="Direct link to API" title="Direct link to API" translate="no">​</a></h6>
<pre class="syntax-highlighter" style="display:block;overflow-x:auto;padding:0.75rem 1rem;color:var(--code-base-color);background:#1c1b1b;border-radius:0.5rem;background-color:var(--code-block-bg);border:1px solid rgba(40, 44, 52, 0.52);line-height:1.4;margin:0.5rem 0"><code class="language-typescript" style="white-space:pre"><span>myProject.runScriptAcrossWorkspaces({
</span><span>  </span><span style="color:var(--code-attr-color)">script</span><span>: </span><span style="color:var(--code-string-color)">"echo 'This is my inline script'"</span><span>,
</span><span>  </span><span style="color:var(--code-attr-color)">inline</span><span>: </span><span style="color:#f08d49">true</span><span>
</span>});</code></pre>
<p>You aren't limited to only the Bun Shell but can also force the system shell, which is <code>sh</code> for Linux/macOS/WSL and <code>cmd</code> for Windows.
You can read more on inline scripts <a href="https://bunworkspaces.com/concepts/inline-scripts" target="_blank" rel="noopener noreferrer" class="">here</a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_HeLj" id="in-conclusion--the-future">In Conclusion &amp; The Future<a href="https://smorsic.io/blog/bun-workspaces-v1#in-conclusion--the-future" class="hash-link" aria-label="Direct link to In Conclusion &amp; The Future" title="Direct link to In Conclusion &amp; The Future" translate="no">​</a></h2>
<p>Above doesn't cover everything that <code>bun-workspaces</code> can currently do.</p>
<p>However, I realize that still many features
should be added to satiate those users that are used to the heavier monorepo frameworks.</p>
<p>I'm one of those users myself, so I will be working continuously towards a more and more advanced feature set, without
sacrificing the no-config easy entry that only requires a valid Bun monorepo.</p>
<p>Now that version 1 has solidified, I'm free to build on top of this foundation that has proven itself for the features thus
far with little technical debt, and I will strive to
keep about the same weekly release cadence that I've kept up for many months already.</p>
<p>Keep an eye out for new releases as I work on the <a href="https://bunworkspaces.com/roadmap" target="_blank" rel="noopener noreferrer" class="">roadmap</a>,
and consider subscribing to the blog at the top right of the page, as I will write new posts for big releases!</p>
<div class="conclusion_G73h"><img src="https://smorsic.io/blog/images/bwunster-blogger_64x70.png" class="pixel-art-image  undefined"></div>]]></content:encoded>
            <category>Release</category>
            <category>bun-workspaces</category>
        </item>
    </channel>
</rss>