Zademy

Nuupa: global dependencies without getting lost in the terminal

Tauri; Rust; Vue
words words

I built Nuupa because I kept repeating a very small mistake.

I would check my globally installed packages, update a few, close the terminal, and later forget what remained outdated. Nothing serious. Just enough friction to happen again and again.

The sensible answer was probably a shell script. Instead, I made a desktop app. I wanted to learn Rust through something useful, fast, and small, without inventing a heroic reason for it.

A small problem deserved a small app

Most of my work lives on the web. That usually means documentation in several tabs, compilation, tests, SonarQube, and similar pipelines waiting to approve whatever I changed. I am used to software passing through browsers, servers, containers, CI jobs, and long terminal output before it feels finished.

Nuupa was refreshing because it felt close. I clicked a button and watched a local package update. I cancelled a run and saw it stop. I built an installer and opened the thing I had made.

The scope is deliberately narrow. Nuupa manages globally installed packages for npm, pnpm, and Bun. It does not touch project dependencies, workspaces, or lockfiles. Those are different problems, and I did not want a small personal tool to slowly become a dependency platform.

It detects the package managers available on the machine, keeps their package spaces separate, and shows installed and latest versions. I can update one package or run the outdated ones sequentially with visible progress. Packages I do not want in the bulk run can be excluded and still updated manually later.

That is enough. I like tools that know when to stop.

Vue made it familiar, Rust made it interesting

Vue 3 was the comfortable side. I already knew how I wanted to shape the interface, and Tauri 2 connected it naturally to a Rust backend that handles commands, process output, and cancellation.

I did not choose Rust because this interface needed extreme performance. It does not. I chose it because I wanted the language to solve a real problem for me instead of living in another disposable exercise.

The useful lessons came from awkward details. npm can return exit code 1 from an outdated check while still printing exactly the information Nuupa needs. Treating every non-zero exit as a useless failure would have been tidy and wrong. npm discovery also has to follow the active Node version when nvm is involved, because “global” changes with that environment.

Updates can be cancelled, but there is no transactional rollback. If three packages finished before I stopped the fourth, those three stay updated. Cancellation means stop continuing, not undo completed work. I prefer saying that plainly rather than giving the interface powers it does not have.

The release workflow changed the mood

The GitHub Actions release workflow became one of my favorite parts, perhaps because it was so different from my usual web routine.

It builds for five targets: macOS arm64 and x64, Linux x64 and arm64, and Windows x64. Seeing one repository produce real desktop artifacts for different systems made the project feel tangible in a new way. A website usually becomes a URL. A desktop app has to meet each operating system and architecture on its own terms.

One Linux arm64 RPM I observed was about 4.26 MB. That is not a benchmark or a universal claim. It was simply pleasant to see a useful app arrive in a small package.

The current observed app version is 0.3.5 in Tauri and Cargo, although the root package.json still says 0.3.1. I will not pretend every manifest is perfectly aligned. Small projects may have fewer places for inconsistencies to hide, but they still try.

What I like

  • It solves the exact annoyance that started it.
  • Its boundary is clear: global packages only.
  • Vue let me move quickly while Rust gave me something real to learn.
  • The desktop release process felt wonderfully direct after so much web work.

What worries me

  • Shell commands inherit differences in paths, runtimes, and operating systems.
  • Cancellation cannot roll back updates that already finished.
  • The version mismatch in the manifests needs cleanup.
  • Extra scope could easily make this simple app much less simple.

For now, this is enough

Nuupa is still a personal app, and that is part of why I enjoy it. It shows me the global packages that need attention and lets me update them without reconstructing yesterday's terminal session.

More importantly, it gave Rust a useful place in my day and gave me a break from the usual web rhythm of docs, builds, tests, and quality pipelines. The project was not effortless, but it was pleasantly direct. I had missed that.

The code is public under AGPL-3.0-or-later in the Nuupa repository.

Resources