@bobfrankston/bobup
List and update all globally-installed @bobfrankston/* npm packages.
Cross-platform — runs on Windows, Linux, and macOS. The CLI uses npm to do all work, and the library spawns npm.cmd on Windows / npm elsewhere automatically.
Requirements
- Node.js ≥ 22 (uses
node:util.styleText)
npm available in PATH
- Permission to install globally (on Linux/macOS that may mean a user-level npm prefix or
sudo, depending on how npm was set up)
Install
npm install -g @bobfrankston/bobup
CLI
bobup Update every outdated package (same as: bobup update all)
bobup -h | --help Show usage
bobup -v | --version Show bobup, npm and node versions
bobup list List packages that need updating (outdated only)
bobup list all List every installed package (alias: bobup listall, bobup list -all)
bobup list -csv Comma-separated names (script-friendly; combine with `all`)
bobup list -json JSON array of package descriptors (combine with `all`)
bobup update all Update every outdated package (alias: bobup upgrade)
bobup update <selectors...> Update by index, range, comma-list, or name
bobup <prod|dev|latest> Update every package to that build (no-arg form)
bobup with no arguments updates every outdated package (equivalent to bobup update all); use bobup -h for usage. On Linux/macOS, if the npm global prefix isn't writable, bobup re-runs itself under sudo automatically (you may be prompted for your password); if sudo isn't available it prints a single clear message instead of a wall of npm EACCES errors. bobup list is concise — only the outdated packages — so the indexes you see are exactly what update will operate on. Pass all (or -all / --all / use listall) to see everything.
Nested dependencies. A package also counts as outdated when a @bobfrankston/* dependency installed inside it is older than the newest registry version its declared range allows — e.g. mlproc 1.0.17 is current but carries mlconfig 1.0.16 while 1.0.17 is published. Publishing mlconfig alone never reaches the copy inside mlproc. bobup list shows such a package as Outdated (reinstall: N deps); bobup list all or -verbose adds a dep line per stale copy; updating reinstalls the same version, and npm re-resolves its dependencies. A newer version outside the declared range is not counted — reinstalling can't reach it; the parent must be republished. Linked (file:) installs are not checked.
Colored output is produced through @bobfrankston/themecolors, which adapts color choices to the detected terminal background — no light-on-light or dark-on-dark surprises.
Script-friendly output
bobup list -csv
bobup list all -csv
bobup list -json
bobup list all -json
-csv and -json are mutually exclusive. Both suppress the human-readable formatting and the broken-package advisory so output stays parseable.
Selectors
Mix and match — all are space-separated; commas are within a single selector.
N | 1-based index against the most recent bobup list (outdated) |
N-M | Inclusive range (against the outdated list) |
N,M,... | Comma list (against the outdated list) |
name | Short or full package name (resolved against ALL installed, so you can force-reinstall a current or broken package by name) |
all | Every outdated package — broken packages are skipped, see below |
Builds / dist-tags
By default bobup installs the latest build, and lists any other published builds (e.g. dev, prod) alongside it so you can see what else is available:
1. @bobfrankston/foo 1.2.0 → 1.2.3 Outdated [dev@1.3.0-dev.2 prod@1.2.1]
Override the build that gets installed with a bare keyword or the general -tag flag — it can appear anywhere on the line:
prod dev latest | bare keyword selects that dist-tag |
-tag <name> | any published dist-tag (also --tag, -tag=<name>) |
When a package has no such dist-tag, bobup falls back to its latest version (so the package still updates) and still lists what builds it does publish. Dist-tag versions come from npm view <name> dist-tags --json — a registry read, so it never needs root.
bobup prod
bobup update all dev
bobup update logit -tag beta
bobup list dev
Examples
bobup
bobup -h
bobup list
bobup list all
bobup update 1
bobup update 1-4
bobup update 1,3-5
bobup update logit npmlatest
bobup update 1,3-5,logit
bobup update all
Each update runs npm install -g @bobfrankston/<name>@<version> (the concrete version resolved for the chosen build) and the npm output streams to your terminal so you see the install progress.
Install scripts
npm 11.17+ has an allowScripts policy that skips install-time lifecycle scripts (preinstall / install / postinstall) for packages not on an allowlist — and global installs have no project package.json to record approvals in, so app postinstall steps (protocol registration, workspace symlinks, dependency binary downloads) would silently never run. Since bobup only installs your own @bobfrankston/* apps, it passes --dangerously-allow-all-scripts so every install script in the tree runs, plus --foreground-scripts so their output is visible.
If the effective npm predates the policy (probed once via npm config get), bobup updates npm itself first — npm install -g npm@latest, automatically, before the package updates. A failed or PATH-shadowed npm self-update is reported but doesn't abort the run: pre-policy npm runs all install scripts unconditionally anyway, so package updates still behave correctly.
Broken installs
If npm list -g can't determine a package's installed version, bobup labels it Broken and shows a diagnostic line under the row (missing directory, missing/unreadable package.json, missing bin shim, etc.).
bobup update all and numeric/range selectors skip broken packages — npm install -g rarely recovers a broken global install on its own, and silently re-running it can leave more debris. Bobup prints the recommended uninstall/reinstall recipe at the end of the run so you can repair them by hand:
npm uninstall -g @bobfrankston/<name> && npm install -g @bobfrankston/<name>
If you still want to force bobup to attempt an install on a broken package, pass it by name (e.g. bobup update brother-print).
Unusable npm cache
If npm fails with EPERM/EACCES inside its cache (e.g. a machine-wide cache under C:\ProgramData\npm\npm-cache that the current user can't write), bobup reruns its npm calls with --cache set to npm's per-user default (%LOCALAPPDATA%\npm-cache, or ~/.npm) and prints a note saying so. Your npm config is not changed; the note gives the lasting fix: npm config set cache "<that folder>".
Registry lookup failures (Unknown)
If npm view <name> dist-tags fails for a package (auth, registry config, network), bobup can't know whether it is current. It labels the row Unknown, prints npm's own error code and summary under it (plus the npm debug-log path when npm gives one), and lists every unchecked package again at the end of the run with the command to reproduce. update all and numeric selectors skip them; naming one forces an install of latest. Any unchecked package makes bobup update exit 1, and Nothing to update — all packages are current is never printed while some couldn't be checked. A package that is a local npm link (file:) copy and was never published gets E404 by design; it is shown as linked, unpublished rather than Unknown. (Before 0.1.18 such failures were shown as current with a bogus [error@[object Object]] build.)
Library API
bobup is also a normal library — useful when another tool wants to drive update logic itself.
import {
listInstalled,
getLatestVersions,
getDistTags,
parseSelectors,
updatePackages,
listAsJson,
Package,
PackageJson,
UpdateResult
} from "@bobfrankston/bobup";
const pkgs = listInstalled();
getLatestVersions(pkgs);
await getDistTags(pkgs, "prod");
const json: PackageJson[] = listAsJson(pkgs);
console.log(JSON.stringify(json, null, 2));
const targets = parseSelectors(["1-3", "logit"], pkgs);
const results: UpdateResult[] = await updatePackages(targets, {
inherit: false,
logmsg: (m) => console.log(m)
});
for (const r of results)
console.log(r.package.name, r.success ? "OK" : r.error);
API summary
listInstalled(): Package[] — globally-installed @bobfrankston/* packages, sorted. Each Package includes a problems: string[] array that is non-empty when npm couldn't determine the installed version (filesystem-level diagnosis is performed automatically for broken installs).
getLatestVersions(pkgs): Package[] — fills latest and target from npm outdated -g (a single call). Mutates and returns input.
getDistTags(pkgs, tag?): Promise<Package[]> — async since 0.1.22, await it. Fills distTags, latest, and target from npm view <name> dist-tags --json (one call per package, 8 at a time, registry-only so no root needed). A failed lookup sets registryError (npm's code/summary) and leaves latest/target blank rather than guessing. tag (default "latest") picks which dist-tag becomes target, falling back to latest when a package doesn't publish that tag. Mutates and returns input.
getStaleDeps(pkgs): Promise<Package[]> — fills staleDeps: nested @bobfrankston/* dependencies older than the newest version their declared range allows. Registry lookups run in parallel (8 at a time). Mutates and returns input.
isOutdated(p): boolean — the single "an update would change this install" test: own version differs from target, or staleDeps is non-empty.
listAsJson(pkgs): PackageJson[] — pure transform that returns JSON-friendly descriptors with a computed status field ("current" | "outdated" | "unknown" | "broken") plus target, distTags and registryError. Run getLatestVersions or getDistTags first if you want the status to reflect outdated state.
parseSelectors(selectors, pkgs): Package[] — resolves ["1-3", "all", "name", ...] against the list. Throws on bad selectors.
updatePackage(pkg, opts?): Promise<UpdateResult> — single update.
updatePackages(pkgs, opts?): Promise<UpdateResult[]> — sequential batch.
UpdateOptions:
inherit?: boolean — if true, npm streams stdio directly to the parent (default false, captured).
logmsg?: (msg: string) => void — optional logger; library never writes to console on its own.
Notes
- Discovery uses
npm list -g --depth=0 --json.
- Build / dist-tag lookup uses
npm view <name> dist-tags --json (one call per package — needed to enumerate every published build). It reads only the registry, so it works without root.
- Updates run sequentially because the npm global prefix isn't safe for concurrent installs.
bobup list -json descriptors include target (the version that would be installed for the chosen build) and distTags (every published build).
-verbose
Add -verbose anywhere on the command line to see each stale dep line, npm's own install output, and the per-package ✓ list after an update. Without it, an update prints the plan and an Updated: N Failed: M count; a failure always prints npm's error.