<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>sigh.dev - Scott Cooper&apos;s dev blog - #typescript</title><description>sigh.dev is Scott Cooper&apos;s dev blog about TypeScript, React, San Francisco, and the web. - Posts tagged with &quot;typescript&quot;</description><link>https://sigh.dev/</link><item><title>Ending my Angular projects</title><link>https://sigh.dev/posts/ending-my-angular-projects/</link><guid isPermaLink="true">https://sigh.dev/posts/ending-my-angular-projects/</guid><description>I’m retiring my Angular libraries.</description><pubDate>Sun, 15 Mar 2026 08:00:00 GMT</pubDate><content:encoded>&lt;section&gt; &lt;div&gt; &lt;div&gt; &lt;div&gt; &lt;div&gt; &lt;div&gt; &lt;div&gt;&lt;/div&gt; &lt;/div&gt;    &lt;/div&gt; &lt;/div&gt; &lt;aside&gt;  &lt;div&gt; &lt;span&gt;H:&lt;/span&gt;&lt;em&gt;°&lt;/em&gt; &lt;span&gt;S:&lt;/span&gt;&lt;em&gt;%&lt;/em&gt; &lt;span&gt;B:&lt;/span&gt;&lt;em&gt;%&lt;/em&gt; &lt;span&gt;R:&lt;/span&gt;&lt;em&gt;&lt;/em&gt; &lt;span&gt;G:&lt;/span&gt;&lt;em&gt;&lt;/em&gt; &lt;span&gt;B:&lt;/span&gt;&lt;em&gt;&lt;/em&gt; &lt;span&gt;#:&lt;/span&gt;&lt;em&gt;&lt;/em&gt; &lt;/div&gt;  &lt;/aside&gt; &lt;/div&gt; &lt;/div&gt; &lt;/section&gt;  
&lt;p&gt;I am retiring the rest of my Angular libraries. I have already marked many of them as archived or deprecated, but this is the last wave. The issues pile in and I have no reason to maintain them anymore.&lt;/p&gt;
&lt;p&gt;What’s weird is that with access to AI, you’d think I’d have more time to maintain them. Or that I could just point an agent at the repo and have it maintain them for me. But what it actually creates is more issues. You still need someone to keep up with the ecosystem and have a vision for the project and understand the nuances of a framework. Should ngx-toastr migrate to &lt;a href=&quot;https://angular.dev/guide/signals&quot; target=&quot;_blank&quot;&gt;Angular signals&lt;/a&gt;? How has sass evolved since I launched ngx-toastr? Does shipping a bootstrap stylesheet for toasts even make sense anymore?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Could your agent implement a better toast component in one or two prompts?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The first Angular 2+ and TypeScript commits I found in my repos are from July 28, 2016. I owe much of my career to Angular turning me on to TypeScript, and it’s been a great 10+ years.&lt;/p&gt;
&lt;p&gt;Fast-forward to today: I haven’t used Angular in over 5 years, and I don’t see myself shifting back. I’m mostly using React and TanStack Start on side projects. At a certain point, it becomes a detriment to an ecosystem to have half-maintained projects, as the forks can’t thrive. I’m not even sure what version Angular is on anymore.&lt;/p&gt;
&lt;p&gt;I got to see some of these libraries make their way into interesting places. ngx-emoji-mart was used in some form in ClickUp, and ngx-toastr was used in the &lt;a href=&quot;https://angular.io/guide/observables&quot; target=&quot;_blank&quot;&gt;Angular documentation&lt;/a&gt; for a while. I also got to see some of these projects get forked and maintained by other people, which is really cool. I hope they continue to thrive.&lt;/p&gt;
  &lt;img src=&quot;/_astro/clickup-emoji.DTMy2h_y_1NPA4w.webp&quot; srcset=&quot;/_astro/clickup-emoji.DTMy2h_y_Z2a21te.webp 640w, /_astro/clickup-emoji.DTMy2h_y_2lKjBM.webp 750w, /_astro/clickup-emoji.DTMy2h_y_Z2m7vuH.webp 828w, /_astro/clickup-emoji.DTMy2h_y_ZvqtzG.webp 1080w, /_astro/clickup-emoji.DTMy2h_y_1NPA4w.webp 1240w&quot; alt=&quot;ngx-emoji-mart in ClickUp | custom ngx-emoji-mart in ClickUp. Did they ever contribute back? No.&quot; loading=&quot;lazy&quot; width=&quot;1240&quot; height=&quot;1356&quot; /&gt;
&lt;h2&gt;&lt;a href=&quot;#what-im-retiring-now&quot;&gt;What I’m Retiring now&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/scttcper/ngx-toastr&quot; target=&quot;_blank&quot;&gt;ngx-toastr&lt;/a&gt; (2.6k★)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/scttcper/ngx-emoji-mart&quot; target=&quot;_blank&quot;&gt;ngx-emoji-mart&lt;/a&gt; (460★)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/scttcper/ngx-color&quot; target=&quot;_blank&quot;&gt;ngx-color&lt;/a&gt; (444★)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/scttcper/ng2-adsense&quot; target=&quot;_blank&quot;&gt;ng2-adsense&lt;/a&gt; (139★)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Where should people go now? I’m not sure. Fork it and make your own.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#what-changed&quot;&gt;What changed&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When I started these libraries, the default move was “install a full component suite,” and you saw libraries like Angular Material and PrimeNG getting millions of downloads. Now the center of gravity is headless primitives plus composition. Teams want control over markup, styling, and behavior instead of inheriting a giant, opinionated package.&lt;/p&gt;
&lt;p&gt;AI shifts that default again. More people will type “make a color component” before installing an off-the-shelf color picker. And frankly, they should. For many apps, that generated component is already good enough and faster to adapt.&lt;/p&gt;
&lt;p&gt;I still think there’s room for good, battle-tested headless primitives, but the bar is higher.&lt;/p&gt;
&lt;p&gt;If you opened issues, sent PRs, or used these packages in production: thanks. I mean that.&lt;/p&gt;
&lt;section&gt; &lt;div&gt; &lt;article&gt; &lt;div&gt;&lt;/div&gt; &lt;div&gt;&lt;/div&gt; &lt;div&gt;&lt;/div&gt; &lt;/article&gt; &lt;article&gt; &lt;div&gt;&lt;/div&gt; &lt;div&gt;&lt;/div&gt; &lt;div&gt;&lt;/div&gt; &lt;/article&gt; &lt;article&gt; &lt;div&gt;&lt;/div&gt; &lt;div&gt;&lt;/div&gt; &lt;div&gt;&lt;/div&gt; &lt;/article&gt; &lt;article&gt; &lt;div&gt;&lt;/div&gt; &lt;div&gt;&lt;/div&gt; &lt;div&gt;&lt;/div&gt; &lt;/article&gt; &lt;/div&gt; &lt;/section&gt;  </content:encoded><category>angular</category><category>typescript</category><category>opensource</category></item><item><title>You can just port things to Cloudflare Workers</title><link>https://sigh.dev/posts/you-can-just-port-things-to-cloudflare-workers/</link><guid isPermaLink="true">https://sigh.dev/posts/you-can-just-port-things-to-cloudflare-workers/</guid><description>Vibecoding, Vibeporting?</description><pubDate>Sun, 25 Jan 2026 08:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This January I decided to double down on using up my &lt;a href=&quot;/posts/addicted-to-free-ai-credits/&quot;&gt;free ai credits&lt;/a&gt; by building a few projects on Cloudflare Workers. I’ve always really liked the idea of this platform that has cheap/free resources, but every time I build larger things on it I run into limits that make me frustrated and move to cheap vps hosting like fly.io or DigitalOcean.&lt;/p&gt;
&lt;p&gt;Is vibeporting a word? Maybe it should be?&lt;/p&gt;
&lt;h1&gt;&lt;a href=&quot;#datasette-ts&quot;&gt;Datasette-ts&lt;/a&gt;&lt;/h1&gt;
&lt;img alt=&quot;Datasette running on Cloudflare Workers | A port of Datasette to Cloudflare Workers&quot; loading=&quot;lazy&quot; width=&quot;1964&quot; height=&quot;1272&quot; src=&quot;/_astro/datasette.lR6-YtED_233bVL.webp&quot; srcset=&quot;/_astro/datasette.lR6-YtED_DHcnO.webp 640w, /_astro/datasette.lR6-YtED_1lQtBx.webp 750w, /_astro/datasette.lR6-YtED_ZYtjVw.webp 828w, /_astro/datasette.lR6-YtED_Z1T6aTX.webp 1080w, /_astro/datasette.lR6-YtED_Z1WrAH6.webp 1280w, /_astro/datasette.lR6-YtED_16XG7w.webp 1668w, /_astro/datasette.lR6-YtED_233bVL.webp 1964w&quot; /&gt;
&lt;p&gt;Over holiday break, I was reading some of &lt;a href=&quot;https://simonwillison.net/&quot; target=&quot;_blank&quot;&gt;Simon Willison’s posts about AI&lt;/a&gt; and I checked where it’s currently possible to deploy it. You can deploy it pretty much everywhere but NOT on Cloudflare Workers, due to workers not really supporting much of Python’s ecosystem. I pointed Codex with GPT-5.2 Codex high/medium at the Datasette repo and had it break it down into README tasks and slowly do them one at a time. I’m not yet convinced by the crazy subagent stuff or that worktrees are worth it.&lt;/p&gt;
&lt;p&gt;As I got into it, it was clear I needed to narrow down the scope. Mr. Willison has built a very mature project that has a plugin system and a ton of features built-in and it depends on subdependencies that he also has published like sqlite-utils. Obviously, what I wanted is something that feels similar and runs on Cloudflare Workers. I picked up Drizzle, Hono, and Alchemy to handle Cloudflare deployments. I chose not to rebuild the frontend as a React SPA instead rendering something similar to the original Jinja templates using Hono’s JSX.&lt;/p&gt;
&lt;p&gt;Anyway, it ends up working pretty well. You can see a live demo at &lt;a href=&quot;https://datasette-legislators.sigh.dev/&quot; target=&quot;_blank&quot;&gt;datasette-legislators.sigh.dev&lt;/a&gt; and the source is available below. I can’t say the code is in a good state, but it lives here &lt;a href=&quot;https://github.com/scttcper/datasette-ts&quot; target=&quot;_blank&quot;&gt;https://github.com/scttcper/datasette-ts&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;&lt;a href=&quot;#sesnoop&quot;&gt;SESnoop&lt;/a&gt;&lt;/h1&gt;
&lt;img alt=&quot;SESnoop running on Cloudflare Workers | A port of Sessy to Cloudflare Workers&quot; loading=&quot;lazy&quot; width=&quot;2712&quot; height=&quot;2030&quot; src=&quot;/_astro/sesnoop.C6_5umH9_6I98D.webp&quot; srcset=&quot;/_astro/sesnoop.C6_5umH9_Z7vDFx.webp 640w, /_astro/sesnoop.C6_5umH9_Z24bGBN.webp 750w, /_astro/sesnoop.C6_5umH9_1wFp8k.webp 828w, /_astro/sesnoop.C6_5umH9_Z8l2QN.webp 1080w, /_astro/sesnoop.C6_5umH9_ZhGSUF.webp 1280w, /_astro/sesnoop.C6_5umH9_Z1FyIf3.webp 1668w, /_astro/sesnoop.C6_5umH9_umF3j.webp 2048w, /_astro/sesnoop.C6_5umH9_Z1aKnlE.webp 2560w, /_astro/sesnoop.C6_5umH9_6I98D.webp 2712w&quot; /&gt;
&lt;p&gt;Another Cloudflare Worker port, this time of a Rails app called &lt;a href=&quot;https://sessy.do&quot; target=&quot;_blank&quot;&gt;Sessy&lt;/a&gt;. I decided to rename it because I was going for a different vibe. I send a bunch of emails via SES for xmplaylist.com and I was looking into running Sessy to handle the bounces and complaints, however I can’t get myself to think about Ruby for more than 1 minute. So again I pointed Codex at the Sessy repo and a few other Cloudflare/Hono example repos and had it build out a monorepo where the worker handles the api and Cloudflare assets serves a React SPA frontend.&lt;/p&gt;
&lt;p&gt;I think what was hard to reel in was how much frontend it was willing to build before I could stop it and start bringing in shadcn components. Like it made a bunch of select components and things that were generally pretty ugly. So then you have to tell it to replace all the ugly things with premade shadcn + baseui components.&lt;/p&gt;
&lt;p&gt;This project took a bit more work to get working outside of pointing AI at the code. It made a number of Cloudflare mistakes, tests were difficult to get working 100% correctly. One example was that it put the webhook at &lt;code&gt;/webhook&lt;/code&gt; which Cloudflare would attempt to load as an asset since the worker is bound to &lt;code&gt;/api&lt;/code&gt; which is always a bit of a headscratcher until you remember how this all works. It was really cool getting to know how SNS works with SES and watching them flow into D1 is fun.&lt;/p&gt;
&lt;p&gt;I tried publishing this package to npm and it kinda works to deploy to cloudflare if alchemy is setup. Or you can run it locally with &lt;code&gt;npx datasette-ts@latest serve ./legislators.db&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;I don’t have a live demo, but you can check out the source code at &lt;a href=&quot;https://github.com/scttcper/sesnoop&quot; target=&quot;_blank&quot;&gt;scttcper/sesnoop&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;&lt;a href=&quot;#conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Pretty happy with how both of these turned out. I didn’t exclusively use Codex on either of them, however Codex was the main driver 95% of the time. I think Codex w/ GPT-5.2 Codex on high or medium is a great model and through the team plan you can basically run medium as long as you want. I exhausted about 1 week worth of the plan I’m on for Codex for each project.&lt;/p&gt;</content:encoded><category>vibecoding</category><category>cloudflare</category><category>typescript</category><category>ai</category></item><item><title>I’m addicted to free AI credits</title><link>https://sigh.dev/posts/addicted-to-free-ai-credits/</link><guid isPermaLink="true">https://sigh.dev/posts/addicted-to-free-ai-credits/</guid><description>Free models, free credits, free VS Code forks. I’m using all of it while it lasts.</description><pubDate>Sun, 14 Dec 2025 08:00:00 GMT</pubDate><content:encoded>&lt;p&gt;You might be too late to enjoy the era of $5 Ubers in SF, but you can still get a ride for free with AI. I’m absolutely hooked on using any available free AI models. Cursor will randomly announce that a new model is free for a week or whatever and I’m fucking there. Remember that time SOTA models were available for free? That was last week, and it’s hard to say how long this sort of spending will continue.&lt;/p&gt;
&lt;p&gt;Opus 4.5 was free for a while, and it did get me hooked on the model. I had tried Sonnet 4.5 a number of times but found it annoyingly cheerful and not as good as GPT-5.1 (granted GPT-5.1 came out way later). Now that I’m faced with having to pay for Opus 4.5, I am certainly tempted and I can see why so many are willing to jump on Claude Code.&lt;/p&gt;
&lt;p&gt;GitHub’s Copilot is giving me their monthly free open source subscription to a limited Copilot plan. I like how they’re still using the old Cursor pricing billed by requests rather than tokens. You can get quite a few things done with that. Google’s antigravity is interesting, but so far it seems to expire before it gets anywhere. I think the one takeaway from chasing free credits is that all the VS Code forks are now basically the same, assuming they have a plan feature. The furthest behind might be Gemini’s VS Code extension, which is a fairly shit experience. I’ve tried some free models and bounced off because they produce more slop than anything useful. Grok’s models are all shit so far.&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;#what-did-i-build&quot;&gt;What did I build?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I launched a paid plan for &lt;a href=&quot;https://xmplaylist.com/pricing&quot; target=&quot;_blank&quot;&gt;xmplaylist.com&lt;/a&gt; and improved a number of my open source projects. I’d say the best use of these free credits has been nice little admin pages that I never would have spent time building. I can point the agent at enough examples that it needs hardly any direction to get it done. I only really do TypeScript in my free time and I’m sometimes jealous of the Django admins in other languages. Slop together a similar admin with TypeScript and &lt;a href=&quot;https://orm.drizzle.team/&quot; target=&quot;_blank&quot;&gt;drizzle&lt;/a&gt; and you’ve likely got a better dev experience than any Django app.&lt;/p&gt;
&lt;p&gt;I’m sure the free tokens will be locked down soon. I’m riding around as much as I can.&lt;/p&gt;
&lt;h4&gt;&lt;a href=&quot;#some-random-predictions-for-2027&quot;&gt;Some random predictions for 2027:&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;People will use smaller models to save money and because they’ve caught up in performance. I’m hopeful for Gemini 3 Flash and not hopeful for SOTA getting cheaper.&lt;/li&gt;
&lt;li&gt;Cursor will have to drop the price on their composer-1 model (which works great)&lt;/li&gt;
&lt;li&gt;At least 2 more VS Code forks will launch&lt;/li&gt;
&lt;li&gt;I’ll still be chasing free SOTA models&lt;/li&gt;
&lt;li&gt;Grok will continue to be 2nd rate&lt;/li&gt;
&lt;li&gt;I’ll actually pay for a personal plan from some provider&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;TODO:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;try windsurf&lt;/li&gt;
&lt;/ul&gt;
  &lt;img src=&quot;/_astro/uber-2017.Dy9Ys4ZO_Z1Cg7kp.webp&quot; srcset=&quot;/_astro/uber-2017.Dy9Ys4ZO_ZDflhY.webp 640w, /_astro/uber-2017.Dy9Ys4ZO_Z220fcE.webp 750w, /_astro/uber-2017.Dy9Ys4ZO_21KIiX.webp 828w, /_astro/uber-2017.Dy9Ys4ZO_Z1epGIr.webp 1080w, /_astro/uber-2017.Dy9Ys4ZO_Z1Cg7kp.webp 1272w&quot; alt=&quot;5 dollar uber | five dollar uber in sf circa 2017. Currently $17 for the same trip&quot; loading=&quot;lazy&quot; width=&quot;1272&quot; height=&quot;1026&quot; /&gt;</content:encoded><category>ai</category><category>typescript</category><category>vibecoding</category></item><item><title>How I Use Hono and Tanstack Start</title><link>https://sigh.dev/posts/how-i-use-hono-and-tanstack-start/</link><guid isPermaLink="true">https://sigh.dev/posts/how-i-use-hono-and-tanstack-start/</guid><description>How I use Hono and Tanstack Start to build my side projects.</description><pubDate>Thu, 20 Nov 2025 08:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I’ve never liked using any of the fullstack web frameworks for apis. They lack clear middleware and routing, they never quite support openapi and the types are often more difficult to work with. I see lots of people use tRPC or oRPC to try to smooth out the rough edges, but most of my side projects rely on heavy caching and RPC is inheirtly shit at caching at the CDN lavel because it makes post requests. Nextjs is the worst offender here, api middleware is a complete nightmare to understand.&lt;/p&gt;
&lt;p&gt;The two options for using hono as I see it are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use vite to start a hono server that loads tanstack start. This was popular how most remix+hono projects tend to be structured.&lt;/li&gt;
&lt;li&gt;Use tanstack start to route api requests to a hono server. This is the pattern I’ve been using for a while now and it’s working well for me.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a href=&quot;#hono-up-front&quot;&gt;Hono up front&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I see the appeal of using hono up front to do all routing, you can inject everything you want and have the last word on the request/response via middleware. &lt;a href=&quot;https://github.com/bskimball/tanstack-hono&quot; target=&quot;_blank&quot;&gt;https://github.com/bskimball/tanstack-hono&lt;/a&gt; shows this off really well. I think the main tradeoffs here are that you have a way more custom vite config and a bit more of a custom build process which might be difficult to maintain especially as tanstack continues to iterate on the vite plugin leading up to the stable release. You’ll notice it runs &lt;code&gt;npm run build:client &amp;amp;&amp;amp; npm run build:server &amp;amp;&amp;amp; npm run build:types&lt;/code&gt; to build which creates different bundles via vite.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#hono-in-the-back&quot;&gt;Hono in the back&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I like to have hono sit in the back during development and then bring it to the front in production, which is a little funky but you only need to set up the production side once. The advantage here is that you don’t need to customize the vite config. Tanstack start can pass requests to hono via an api wildcard route.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// src/routes/api/$.ts wildcard api route&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { createFileRoute } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;@tanstack/react-router&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { app } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;../../../server/app&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; serve&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async&lt;/span&gt;&lt;span&gt; ({ &lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; { request&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Request&lt;/span&gt;&lt;span&gt; }) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; app.&lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt;(request);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// Route all api requests to the hono server&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; Route &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; createFileRoute&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;/api/$&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  server&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    handlers&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      GET&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; serve,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      POST&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; serve,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      PUT&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; serve,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      DELETE&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; serve,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      PATCH&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; serve,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      OPTIONS&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; serve,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      HEAD&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; serve,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded><category>typescript</category><category>hono</category><category>tanstack-start</category></item><item><title>My Favorite NPM Command</title><link>https://sigh.dev/posts/favorite-npm-command/</link><guid isPermaLink="true">https://sigh.dev/posts/favorite-npm-command/</guid><description>An ode to `npm repo`, the best npm command.</description><pubDate>Sat, 11 Oct 2025 08:00:00 GMT</pubDate><content:encoded> 
&lt;p&gt;Let’s talk about my favorite npm command, &lt;code&gt;npm repo&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Open package repository page in the browser - &lt;a href=&quot;https://docs.npmjs.com/cli/v11/commands/npm-repo&quot; target=&quot;_blank&quot;&gt;npm docs&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This command is dead simple. Open the repository page for the given package in the browser. If you omit the package name, it will look at the local package.json and open that repository.&lt;/p&gt;
&lt;p&gt;I use this command several times a day—often hourly. What’s great is that it works for published npm packages and any repo with a &lt;code&gt;package.json&lt;/code&gt;. I’ll use it just to open the repo I’m working in, too. I don’t need a bunch of bookmarks or to hope that my current repo shows up on the GitHub homepage. I’ll just run &lt;code&gt;npm repo&lt;/code&gt; and dig into any package, and I’ve yet to be rickrolled. Googling certain packages is annoying because you often have to add “GitHub repo” to the search.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#history&quot;&gt;History&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The npm repo command was added in 2013 in commit &lt;a href=&quot;https://github.com/npm/npm/commit/0223389130dfd220d2a18dfe7058c4f7f0b14808&quot; target=&quot;_blank&quot;&gt;0223389&lt;/a&gt; by &lt;a href=&quot;https://github.com/tj&quot; target=&quot;_blank&quot;&gt;TJ Holowaychuk&lt;/a&gt;, creator of Express, Mocha, and other popular npm packages. Ignoring various issues opened on the CLI, this is the only contribution he made directly.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#other-package-managers&quot;&gt;Other package managers&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The yarn cli does not have a repo command. Pnpm does not have a repo command, instead &lt;a href=&quot;https://github.com/pnpm/pnpm/blob/1b15e45ae949485ba9b9bfd4e079b551ab7d8819/pnpm/src/pnpm.ts#L30&quot; target=&quot;_blank&quot;&gt;they forward the command to the npm cli&lt;/a&gt; among a list of other commands they forward. &lt;code&gt;pnpm home&lt;/code&gt; is another good one they forward to npm, I just don’t usually reach for that one.&lt;/p&gt;
&lt;p&gt;As I move further away from using Koa, Express, and TJ’s other legacy projects, I’m positive I’ll keep using &lt;code&gt;npm repo&lt;/code&gt;. Praise TJ.&lt;/p&gt;</content:encoded><category>typescript</category><category>npm</category><category>tj</category></item><item><title>@ctrl/tinycolor Supply Chain Attack Post-mortem</title><link>https://sigh.dev/posts/ctrl-tinycolor-post-mortem/</link><guid isPermaLink="true">https://sigh.dev/posts/ctrl-tinycolor-post-mortem/</guid><description>Lessons learned from becoming the unexpected face of a npm supply-chain attack.</description><pubDate>Tue, 16 Sep 2025 08:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;&lt;a href=&quot;#tldr&quot;&gt;TL;DR&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A malicious GitHub Actions workflow was pushed to a shared repo and exfiltrated a npm token with broad publish rights. The attacker then used that token to publish malicious versions of 20 packages, including &lt;code&gt;@ctrl/tinycolor&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;My GitHub account, the @ctrl/tinycolor repository were not directly compromised. There was no phishing involved, and no malicious packages were installed on my machine and I already use pnpm to avoid unapproved postinstall scripts. There was no pull request involved because a repo admin does not need a pull request to add new github actions.&lt;/p&gt;
&lt;p&gt;GitHub/npm security responded quickly, unpublishing the malicious versions. I followed by releasing clean versions to flush caches, as advised.&lt;/p&gt;
&lt;p&gt;For broader context, see &lt;a href=&quot;https://socket.dev/blog/tinycolor-supply-chain-attack-affects-40-packages&quot; target=&quot;_blank&quot;&gt;Socket’s write-up&lt;/a&gt; or &lt;a href=&quot;https://www.stepsecurity.io/blog/ctrl-tinycolor-and-40-npm-packages-compromised&quot; target=&quot;_blank&quot;&gt;StepSecurity’s analysis&lt;/a&gt;. For community discussion, see this &lt;a href=&quot;https://news.ycombinator.com/item?id=45260741&quot; target=&quot;_blank&quot;&gt;Hacker News post&lt;/a&gt;, which spent 24 hours on the front page. I’m also finding this &lt;a href=&quot;https://www.wiz.io/blog/shai-hulud-npm-supply-chain-attack&quot; target=&quot;_blank&quot;&gt;wiz.io&lt;/a&gt; post helpful.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#how-i-found-out&quot;&gt;How I Found Out&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;On September 15 around 4:30
 PM PT, &lt;a href=&quot;https://bsky.app/profile/notwes.bsky.social&quot; target=&quot;_blank&quot;&gt;Wes Todd&lt;/a&gt; DM’d me on Bluesky and looped me into the OpenJS Foundation Slack. By that point, Wes had already alerted GitHub/npm security, who were compiling lists of affected packages and rapidly unpublishing compromised versions.&lt;/p&gt;
&lt;p&gt;Early guidance (attributed to Daniel Pereira) was to look for suspicious &lt;code&gt;Shai-Hulud&lt;/code&gt; repos or branches. I wasn’t able to find any of these repos or branches on my own personal repos. The mystery was: how was I impacted at all?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Shai-Hulud was the Fremen term for the sandworm of Arrakis. - &lt;a href=&quot;https://dune.fandom.com/wiki/Shai-Hulud&quot; target=&quot;_blank&quot;&gt;dune wiki&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;&lt;a href=&quot;#what-actually-happened&quot;&gt;What Actually Happened&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A while ago, I collaborated on &lt;a href=&quot;https://github.com/angulartics/angulartics2&quot; target=&quot;_blank&quot;&gt;angulartics2&lt;/a&gt;, a shared repository where multiple people still had admin rights. That repo still contained a GitHub Actions secret — a npm token with broad publish rights. This collaborator had access to projects with other people which I believe explains some of the other 40 initial packages that were affected.&lt;/p&gt;
&lt;p&gt;A new Shai-Hulud branch was force pushed to angulartics2 with a malicious github action workflow by a collaborator. The workflow ran immediately on push (did not need review since the collaborator is an admin) and stole the npm token. With the stolen token, the attacker published malicious versions of 20 packages. Many of which are not widely used, however the @ctrl/tinycolor package is downloaded about 2 million times a week.&lt;/p&gt;
&lt;p&gt;GitHub and npm security teams moved quickly to unpublish the malicious versions. I then re-published fresh, verified versions of the packages I maintain to flush caches and restore trust.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#impact&quot;&gt;Impact&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Malicious versions of several packages — including @ctrl/tinycolor — were briefly available on npm before removal. Installing those compromised versions would have triggered a postinstall payload, which is documented in detail by &lt;a href=&quot;https://www.stepsecurity.io/blog/ctrl-tinycolor-and-40-npm-packages-compromised#attack-mechanism&quot; target=&quot;_blank&quot;&gt;StepSecurity&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;What should you do if you’ve installed a compromised version of a package? &lt;a href=&quot;https://www.stepsecurity.io/blog/ctrl-tinycolor-and-40-npm-packages-compromised#immediate-actions-required&quot; target=&quot;_blank&quot;&gt;see StepSecurity’s immediate actions&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#publishing-setup--interim-plan&quot;&gt;Publishing Setup &amp;amp; Interim Plan&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I currently use &lt;a href=&quot;https://github.com/semantic-release/semantic-release&quot; target=&quot;_blank&quot;&gt;semantic-release&lt;/a&gt; with GitHub Actions to handle publishing. The automation is convenient and predictable. I also have npm provenance enabled on many packages, which provides attestations of how they were built. Unfortunately, provenance didn’t prevent this attack because the attacker had a valid token.&lt;/p&gt;
&lt;p&gt;My goal is to move to npm’s &lt;strong&gt;Trusted Publishing (OIDC)&lt;/strong&gt; to eliminate static tokens altogether. However, semantic-release integration is still in progress: &lt;a href=&quot;https://github.com/npm/cli/issues/8525&quot; target=&quot;_blank&quot;&gt;npm/cli#8525&lt;/a&gt;.&lt;/p&gt;
&lt;img alt=&quot;npm Publishing access settings&quot; loading=&quot;lazy&quot; width=&quot;1618&quot; height=&quot;804&quot; src=&quot;/_astro/publishing-access.DTmYbTkJ_2szCyf.webp&quot; srcset=&quot;/_astro/publishing-access.DTmYbTkJ_Z17qXC3.webp 640w, /_astro/publishing-access.DTmYbTkJ_eXCMM.webp 750w, /_astro/publishing-access.DTmYbTkJ_9Y9Pv.webp 828w, /_astro/publishing-access.DTmYbTkJ_ZVUVuC.webp 1080w, /_astro/publishing-access.DTmYbTkJ_Z1hp1B7.webp 1280w, /_astro/publishing-access.DTmYbTkJ_2szCyf.webp 1618w&quot; /&gt;
&lt;p&gt;For the forseeable future, @ctrl/tinycolor requires 2FA for publishing, and all tokens have been revoked. Not expecting to merge any new changes anytime soon.&lt;/p&gt;
&lt;p&gt;For smaller packages, I’ll continue using semantic-release but under stricter controls: no new contributors will be added, and each repo will use a granular npm token limited to publish-only rights for that specific package.&lt;/p&gt;
&lt;p&gt;Local 2FA based publishing isn’t sustainable, so I’m watching OIDC/Trusted Publishing closely and will adopt it as soon as it fits the workflow.&lt;/p&gt;
&lt;p&gt;I plan to continue using pnpm that prevents unapproved postinstall scripts from being run and I’ll look into adding pnpm’s new &lt;a href=&quot;https://pnpm.io/settings#minimumreleaseage&quot; target=&quot;_blank&quot;&gt;minimumReleaseAge&lt;/a&gt; setting.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#publishing-wishlist&quot;&gt;Publishing Wishlist&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If I could wave a magic wand and design my ideal setup, npm would allow me to require Trusted Publishing (OIDC) with a single toggle for all of my packages. That same toggle would block any release missing provenance, enforcing security at the account level. I’d also want first-class semantic-release support with OIDC and provenance so no static tokens are ever needed.&lt;/p&gt;
&lt;p&gt;On top of that, I’d like a secure, human-approved publishing option directly in the GitHub UI: a protected workflow_dispatch flow that uses github 2FA approval to satisfy 2FA, without requiring me to publish from my laptop.&lt;/p&gt;
&lt;p&gt;GitHub Environments — or equivalent workflow protections — should be available without a Pro subscription, or else integrated directly into Trusted Publishing so that security doesn’t depend on the pricing tier.&lt;/p&gt;
&lt;p&gt;It would be really nice if NPM also had a more visible mark on the package details page to indicate if the package had a postinstall script. Also, once the packages are pulled its not clear what versions were removed and why.&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;#thanks&quot;&gt;Thanks&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Thanks to Wes Todd, the OpenJS Foundation, and the GitHub/npm security teams for their rapid and coordinated response. Everyone was incredibly fast, helpful, and knowledgeable.&lt;/p&gt;
&lt;img alt=&quot;dune worm [wide] | dune worm via chatgpt&quot; loading=&quot;lazy&quot; width=&quot;1536&quot; height=&quot;1024&quot; src=&quot;/_astro/dune-worm.QN2JLFkT_ZrwTtI.webp&quot; srcset=&quot;/_astro/dune-worm.QN2JLFkT_Z10Rrz2.webp 640w, /_astro/dune-worm.QN2JLFkT_Z27HXTE.webp 750w, /_astro/dune-worm.QN2JLFkT_1yGcEY.webp 828w, /_astro/dune-worm.QN2JLFkT_ZrrC3Y.webp 1080w, /_astro/dune-worm.QN2JLFkT_1JkG00.webp 1280w, /_astro/dune-worm.QN2JLFkT_ZrwTtI.webp 1536w&quot; /&gt;</content:encoded><category>typescript</category><category>security</category></item></channel></rss>