npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

vite-plugin-app-version

v0.3.0

Published

Vite plugin to generate an app version file (version.json) and optional virtual module with git/tag/build metadata.

Readme

vite-plugin-app-version

npm version license

Generate an app version.json plus an optional virtual:app-version module with git/tag/build metadata — for build-info display, cache-busting, and runtime "new version available" checks. Zero dependencies, ESM + CJS.


Features

  • 🏷️ Emits version.json with git tag/commit, package.json version, build time, and mode.
  • 🧩 Optional virtual:app-version module: import version, { checkVersion, onCheck, getChangelog } from 'virtual:app-version'.
  • 🧭 Pluggable versionStrategyauto (default), git-tag, package-json, or your own function.
  • 📜 Optional changelog.json — generated from git commits (conventional-commit aware), a CHANGELOG.md, an explicit list, or a custom function.
  • 🧪 Dev preview with no-store + weak ETag (304), served under your Vite base.
  • 🧷 Typed — ship-with client.d.ts (zero-config) or opt-in precise generated types via emitDtsTo.
  • 🔀 extraFields are fully type-inferred at the call site.
  • 🟢 Zero runtime dependencies — and it never writes into your src/ unless you ask it to.

Installation

npm i -D vite-plugin-app-version
# or: pnpm add -D vite-plugin-app-version
# or: yarn add -D vite-plugin-app-version

Requires Node ≥ 18 and Vite ≥ 4.


Usage

// vite.config.ts
import { defineConfig } from 'vite';
import { generateVersion } from 'vite-plugin-app-version';

export default defineConfig({
  plugins: [
    generateVersion({
      extraFields: {
        env: process.env.NODE_ENV,
        apiUrl: 'https://api.example.com',
        release: 42,
      },
    }),
  ],
});

| Phase | Behavior | |-------|----------| | dev (vite) | version.json served at its route under your Vite base with no-store + weak ETag. | | build (vite build) | Emitted as a build asset to the root of your build outDir. | | outputDir | Also written to disk (dev & build), skipped when unchanged. | | changelog | When enabled, a changelog.json is emitted/served the same way, alongside version.json. |

Example version.json

{
  "pkgVersion": "9.9.9",
  "version": "v2.5.0",
  "commitShort": "a10d744",
  "buildTime": "2026-06-07T15:01:37.037Z",
  "mode": "production",
  "env": "production",
  "apiUrl": "https://api.example.com",
  "release": 42
}

The virtual:app-version module

import version, { checkVersion, onCheck, getChangelog } from 'virtual:app-version';

console.log('Build:', version.version, version.buildTime);

// React to deploys: compares the deployed version.json against this build.
const off = onCheck(({ updated, latest }) => {
  if (updated) console.log('🔄 New version available:', latest?.version);
});

setInterval(checkVersion, 60_000);
// off(); // unsubscribe

// Fetch the deployed changelog.json (newest release first; [] if disabled/unreachable).
const releases = await getChangelog();

| Export | Signature | Description | |--------|-----------|-------------| | version (default) | AppVersion | This build's inlined version metadata. | | checkVersion | () => Promise<{ updated, latest }> | Fetches the deployed version.json and reports whether it differs from this build. | | onCheck | (cb) => () => void | Subscribes to every checkVersion() result; returns an unsubscribe function. | | getChangelog | () => Promise<ChangelogEntry[]> | Fetches the deployed changelog.json. Returns [] when the changelog is disabled or unreachable. |

Types

Add the shipped ambient types (the standard Vite pattern) — either a triple-slash reference in any .d.ts/entry file:

/// <reference types="vite-plugin-app-version/client" />

or via tsconfig.json:

{ "compilerOptions": { "types": ["vite-plugin-app-version/client"] } }

This types the standard fields and permits custom ones (loose index signature).

Precise types for extraFields (opt-in)

For exact typing of your custom fields, generate a declaration with emitDtsTo:

generateVersion({
  emitDtsTo: 'src/app-version.d.ts', // written only when its content changes
  extraFields: { release: 42, env: 'prod' },
});

produces:

declare module 'virtual:app-version' {
  export interface AppVersion {
    pkgVersion: string | null;
    version: string;
    commitShort: string | null;
    buildTime: string;
    mode: string;
    release: number; // ← exact
    env: string;     // ← exact
  }
  // ...checkVersion / onCheck / getChangelog / default export
}

Unlike v0.2, the plugin does not write any file into your source tree by default. Generation happens only when you set emitDtsTo, and only when the content actually changes.


Options

generateVersion<Extra>(options?: AppVersionOptions<Extra>): Plugin

| Option | Type | Default | Description | |--------|------|---------|-------------| | filename | string | "version.json" | Output filename. | | publicFields | AppVersionField[] | ["pkgVersion","version","commitShort","buildTime","mode"] | Which standard fields to expose. | | exposeVirtual | boolean | true | Enable the virtual:app-version module. | | extraFields | Record<string, unknown> | {} | Custom fields merged into the output (and inferred into types). | | outputDir | string | — | Also mirror version.json (and changelog.json) to this directory on disk. | | emitDtsTo | string | — | Opt-in: write a precise .d.ts for the virtual module to this path. | | versionStrategy | VersionStrategy | "auto" | How the version string is resolved. See Version strategy. | | changelog | boolean \| ChangelogOptions | false | Emit a changelog.json and expose getChangelog(). See Changelog. |

Standard fields (AppVersionInfo)

interface AppVersionInfo {
  version: string;        // git exact tag → git describe → short commit → pkgVersion → timestamp
  commitShort: string | null;
  pkgVersion: string | null;
  buildTime: string;      // ISO timestamp
  mode: string;           // Vite mode
}

Git is detected via a statically-imported execSync, so it works in both the ESM and CJS builds. Outside a git work tree, version falls back to pkgVersion, then a timestamp.


Version strategy

versionStrategy controls how the human-readable version string is resolved. Any strategy that yields nothing (null/empty) gracefully degrades to the auto chain, so version is always non-empty.

| Strategy | Resolves to | |----------|-------------| | "auto" (default) | git describe (exact tag → nearest tag → short commit) → package.json version → build timestamp. | | "git-tag" | The tag on HEAD → nearest tag → short commit. | | "package-json" | The version field from package.json. | | (ctx) => string \| null | Full control. Receives a VersionContext; return null to fall back to auto. |

generateVersion({
  versionStrategy: (ctx) => `${ctx.pkgVersion}+${ctx.commitShort}`,
});
interface VersionContext {
  pkgVersion: string | null;   // package.json version
  gitDescribe: string | null;  // git describe (exact tag → nearest → short commit)
  gitTag: string | null;       // tag on HEAD, when HEAD sits exactly on a tag
  commitShort: string | null;  // short git commit hash
  mode: string;                // Vite mode
  root: string;                // resolved project root
}

Changelog

Opt in with changelog to emit a changelog.json (served/emitted alongside version.json) and expose getChangelog() on the virtual module. Pass true for the default git source, or an object to choose the source and tune output.

generateVersion({ changelog: true });                  // git commits, grouped by tag
generateVersion({ changelog: { strategy: 'file' } });  // parse CHANGELOG.md
generateVersion({ changelog: { limit: 10 } });         // keep the 10 newest releases

| Strategy | Source | |----------|--------| | "git" (default) | Commits grouped by tag, parsed as conventional commits (feat, fix(scope):, feat!:…). Commits after the newest tag form an "Unreleased" entry. | | "file" | A Keep a Changelog style CHANGELOG.md (path overrides the location). | | "config" | The explicit entries array you pass in. | | (ctx) => ChangelogEntry[] | Full control; sync or async. |

ChangelogOptions

| Option | Type | Default | Description | |--------|------|---------|-------------| | strategy | "git" \| "file" \| "config" \| function | "git" | Where entries come from. | | filename | string | "changelog.json" | Asset filename written/served. | | path | string | "CHANGELOG.md" | "file" strategy: markdown path (relative to root). | | entries | ChangelogEntry[] | — | "config" strategy: the explicit entry list. | | limit | number | — | Cap the number of releases (newest kept). |

Example changelog.json

[
  {
    "version": "Unreleased",
    "date": null,
    "changes": [
      { "type": "feat", "scope": "api", "message": "add changelog support", "hash": "a10d744" }
    ]
  },
  {
    "version": "v2.5.0",
    "date": "2026-06-07T15:01:37.000Z",
    "changes": [
      { "type": "fix", "scope": null, "message": "handle missing tags", "hash": "9f3c2b1" }
    ]
  }
]
interface ChangelogEntry {
  version: string;          // tag label, or "Unreleased"
  date: string | null;      // ISO date, or null when unknown
  changes: ChangelogChange[];
}
interface ChangelogChange {
  type: string | null;      // conventional-commit type / Keep-a-Changelog heading
  scope?: string | null;    // conventional-commit scope, when present
  message: string;
  hash?: string | null;     // short commit hash, when sourced from git
}

License

MIT © dev.zarghami