Crust logoCrust

Update Notifier

Check npm's latest dist-tag for a newer version after a command succeeds.

Usage

cli.ts
import { Crust } from "@crustjs/core";
import { updateNotifier } from "@crustjs/extensions";

const app = new Crust("my-cli", { version: "1.2.3" })
	.extend(
		updateNotifier({
			packageName: "my-cli",
			updateCommand: { scope: "global" },
		}),
	)
	.action(({ stdout }) => stdout("Done"));

await app.execute();

When the registry reports version 2.0.0, the action writes Done to stdout and the hook writes this notice to stderr, so piped stdout stays clean:

╭──────────────────────────────────────╮
│                                      │
│   Update available  1.2.3 → 2.0.0    │
│   Run npm install -g my-cli@latest   │
│                                      │
╰──────────────────────────────────────╯

The hook checks only after a completed Command Action. Network, registry, cache, and response failures do not change a successful outcome.

API

Prop

Type

The current version must come from currentVersion or root metadata. If neither exists, the hook throws DEFINITION before network or cache work.

The network timeout does not bound cache operations or the whole hook. Version comparison follows SemVer precedence, and notices for the same latest version are suppressed across sequential cached invocations.

Update command

updateNotifier({
  packageName: "my-cli",
  updateCommand: { scope: "global" },
});

A string displays a fixed command. A resolver receives { packageName, packageManager }, while a scope creates the standard command for npm, pnpm, Yarn, or Bun. Resolvers and package-manager detection run only when a notice is shown.

updateNotifier({
  packageName: "my-cli",
  updateCommand: ({ packageName, packageManager }) => `${packageManager} run update-${packageName}`,
});

Package-manager detection checks npm_config_user_agent, then npm_execpath, then the runtime executable name, and defaults to npm. Yarn global updates use npm install -g <package>@latest.

Cache

updateNotifier({
  packageName: "my-cli",
  cache: { intervalMs: 60 * 60 * 1000 },
});

The default adapter stores state under the package's platform state directory. Set cache: false to skip all cache reads and writes.

Prop

Type

cache-adapter.ts
import {
	updateNotifier,
	type UpdateNotifierCacheAdapter,
	type UpdateNotifierState,
} from "@crustjs/extensions";

let state: UpdateNotifierState | undefined;
const adapter: UpdateNotifierCacheAdapter = {
	read: async () => state,
	write: async (nextState) => {
		state = nextState;
	},
};

updateNotifier({ packageName: "my-cli", cache: { adapter } });

A custom adapter reads UpdateNotifierState | null | undefined and writes UpdateNotifierState. Adapter failures are treated as soft failures.

On this page