upd package - github.com/LarsArtmann/upd - Go Packages

upd

package module
v1.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 15, 2026 License: MIT Imports: 24 Imported by: 0

README

UPD

Upgrade NPM Package Dependencies — fast, safe, formatting-preserving.

CI Go Report Card Go Reference

A Go CLI that bumps dependency versions in an NPM package.json while byte-preserving all original JSON formatting — whitespace, key order, and quoting style stay exactly as you wrote them. Only the version number inside each constraint string changes. Nothing else is touched.

Go rewrite of rse/upd — the original JavaScript/Node.js CLI by Dr. Ralf S. Engelschall.


Demo

upd demo

Made with VHS

Features

  • Formatting-preserving edits — surgically replaces only the version bytes inside each constraint string. Your indentation, key order, and quoting style are never touched.
  • TOCTOU-safe atomic writes — stages a temp file, fsyncs it, verifies the on-disk fingerprint hasn't changed since read, then atomically renames. If another process (pnpm install, IDE formatter) edited package.json during the network-fetch window, the write is aborted and your file is left untouched.
  • Concurrent registry queries — fetches packuments in parallel with a configurable connection pool (default 8).
  • Semantic version resolution — resolves to dist-tags.latest by default, or the highest semver across all published versions with -g.
  • Pin latest tags-P / --pin-latest rewrites bare "latest" tags to their exact resolved semver (e.g. "latest""7.7.4").
  • Glob pattern filtering — update only matching dependencies, with !-prefixed exclusions (e.g. upd react* !react-dom).
  • All four dependency sectionsdependencies, devDependencies, peerDependencies, optionalDependencies.
  • Character-level diff highlighting — shows exactly which characters changed in red/green.
  • Embedded defaults — an "upd" field in package.json supplies default CLI arguments so you don't repeat yourself.
  • Styled CLI help — built with charm.land/fang/v2 + Cobra: color-coded help, usage examples, man pages (upd man), and shell completions (upd completion <bash|zsh|fish>).
  • Single static binary — no runtime dependencies, no Node.js required.

Quick Start

# Install (requires Go 1.26+ with GOEXPERIMENT=jsonv2)
GOEXPERIMENT=jsonv2 go install github.com/LarsArtmann/upd/cmd/upd@latest

# Dry run — show what would change without writing
upd -n

# Apply updates
upd

Why GOEXPERIMENT=jsonv2? upd uses Go's encoding/json/v2 for byte-precise JSON editing. This flag will become unnecessary once the Go team stabilizes the json/v2 package in a future release.

Example output (upd -n -C):

┌─────────────────────────────────────┬──────────────┬──────────────┬─────────┐
│MODULE NAME                          │VERSION OLD   │VERSION NEW   │STATE    │
├─────────────────────────────────────┼──────────────┼──────────────┼─────────┤
│express                              │^4.18.0       │^5.2.1        │updated  │
│jest                                 │^29.5.0       │^30.4.2       │updated  │
│lodash                               │^4.17.20      │^4.18.1       │updated  │
│typescript                           │^5.2.0        │^7.0.2        │updated  │
└─────────────────────────────────────┴──────────────┴──────────────┴─────────┘

Installation

Go
GOEXPERIMENT=jsonv2 go install github.com/LarsArtmann/upd/cmd/upd@latest

Requires Go 1.26+ with the json/v2 experiment enabled.

Nix
# Run directly without installing
nix run github:LarsArtmann/upd

# Install to your profile
nix profile install github:LarsArtmann/upd
Build from source
git clone https://github.com/LarsArtmann/upd.git
cd upd
nix run .#build    # or: GOEXPERIMENT=jsonv2 go build -o upd ./cmd/upd

Usage

upd [-h] [-V] [-q] [-n|--dry-run] [-C] [-f <file>] [-r <registry>] [-g] [-a] [-c <concurrency>] [-P] [-t <timeout>] [--retries <n>] [--json] [--verbose] [<pattern> ...]
Flag Long form Description
-h --help Show usage help.
-V --version Show program version.
-q --quiet Suppress output (no progress bar, no table, no warnings).
-n --nop Dry run — do not modify package.json.
--dry-run Alias for --nop.
-C --no-color Disable ANSI colors in output. --noColor is a hidden alias.
-f --file Path to package config (default: package.json).
-r --registry NPM registry base URL (default: registry.npmjs.org).
-g --greatest Use greatest published version instead of latest tag.
-a --all Show all packages, not just updated ones.
-c --concurrency Concurrent NPM registry connections (default: 8).
-P --pin-latest Pin bare latest tags to exact semver.
-t --timeout Per-request timeout (default: 20s).
--retries Max retries for transient 429/5xx failures (default: 3).
--json Machine-readable JSON output for CI/scripts.
--verbose Show full error chains in the error detail block.
<pattern> Glob pattern for dependency names. ! prefix excludes.

Color auto-detection: Colors are automatically disabled when the NO_COLOR environment variable is set (see no-color.org) or when stdout is not a terminal (piped/redirected). Use -C to force-disable.

Examples:

upd                          # update all dependencies
upd -n                       # dry run (preview changes)
upd react*                   # only update packages matching "react*"
upd react* !react-dom        # update react packages except react-dom
upd -g                       # use greatest version (include pre-releases)
upd -P                       # pin "latest" tags to exact versions
upd -c 16 lodash*            # 16 concurrent connections, only lodash
upd -f alt.json -n           # preview changes to alt.json

How It Works

Byte-preserving edits

upd parses package.json with a streaming JSON decoder that tracks byte offsets. When a version needs updating, it splices only the version bytes out of the raw file and inserts the new ones. The rest of the file — every space, newline, key ordering, and quoting choice — is preserved exactly. No re-serialization, no formatter arguments, no diffs to resolve.

Version resolution

Each dependency constraint is classified by a regex:

  • Upgradable — strings starting with a digit, optionally preceded by ^ or ~ (e.g. 1.2.3, ^1.2.3, ~2.3.4, 1.x). The prefix is preserved on update: ^1.2.3^2.0.0.
  • Skipped — comparator ranges (>=1.0.0), tags (latest), git/file URLs — anything containing <>|=. Use -P to opt-in to pinning latest.
  • Ignored — names that don't match any supplied glob pattern.

After resolving the target version from the NPM registry, a semver comparison guards against downgrades: if the resolved version isn't actually newer, the dependency is marked kept.

Atomic writes with TOCTOU protection

The write path uses go-atomic-write to prevent data loss when another process edits package.json during the network-fetch window. It stages a temp file, fsyncs it, acquires a cross-platform file lock, verifies the on-disk fingerprint hasn't changed since read, then atomically renames. On mismatch it aborts with ErrConcurrentModification — the file is untouched.

See docs/atomic-writes.md for the full step-by-step breakdown and flow diagram.

Configuration

Embedded upd field

Add an "upd" field to your package.json to supply default arguments that are prepended to CLI flags:

{
	"upd": ["react*", "!react-dom", "-c", "16"],
	"dependencies": {
		"react": "^18.0.0",
		"react-dom": "^18.0.0",
		"lodash": "^4.17.20"
	}
}

Now upd is equivalent to upd react* !react-dom -c 16. CLI flags override or supplement these defaults. The field accepts a string or an array.

Environment variables

Every public flag can also be set via an environment variable with the UPD_ prefix. CLI flags always override environment variables.

Env var Equivalent flag
UPD_REGISTRY --registry
UPD_FILE --file
UPD_TIMEOUT --timeout
UPD_CONCURRENCY --concurrency
UPD_RETRIES --retries
UPD_QUIET --quiet
UPD_NOP --nop
UPD_DRY_RUN --dry-run
UPD_NO_COLOR --no-color
UPD_GREATEST --greatest
UPD_ALL --all
UPD_PIN_LATEST --pin-latest
UPD_JSON --json
UPD_VERBOSE --verbose

For example:

UPD_REGISTRY=https://my-registry.example.com upd -n

Shell Completions

Generate shell completions for bash, zsh, or fish:

upd completion bash > /etc/bash_completion.d/upd
upd completion zsh > /usr/share/zsh/site-functions/_upd
upd completion fish > ~/.config/fish/completions/upd.fish

Exit Codes

Code Meaning
0 Success — all dependencies resolved without errors.
1 Failure — package not found, partial resolution errors, IO error, or malformed input.
75 Registry unavailable — transient 5xx/timeout from the NPM registry. Retryable (EX_TEMPFAIL).

In CI, check for exit 75 to decide whether to retry the job. Exit 1 means something needs human attention (typo in dependency name, malformed JSON, etc.).

Troubleshooting

ERROR: package not found in NPM registry (exit 1) The package name in package.json doesn't exist on the registry. Check for typos, check if the package was unpublished, or verify you're using the correct registry (-r/--registry).

ERROR: NPM registry is unavailable (exit 75) The registry returned a server error (5xx) or timed out. This is transient — re-run upd after a few seconds. If using a private registry, verify it's running and accessible. Use --retries to increase the number of retry attempts.

ERROR: package configuration file was modified concurrently Another process (pnpm install, IDE auto-save, formatter) edited package.json while upd was fetching versions. Your file was not changed. Simply re-run upd.

ERROR: invalid JSON in package configuration file Your package.json has malformed JSON. Run pnpm dlx jsonlint package.json or node -e "JSON.parse(require('fs').readFileSync('package.json','utf8'))" to find the syntax error.

Progress bar is garbled or leaves artifacts The progress bar is cleared using a fixed-width reset. If your terminal is narrower than 80 characters, use -q (quiet mode) to suppress it.

Colors appear in piped output Colors are auto-disabled when stdout is not a terminal or NO_COLOR is set. If you still see colors, ensure no tool in your pipeline (e.g. script) is emulating a TTY. You can always force-disable with -C.

Development

This repo uses Nix flakes for all build automation:

nix run .#build          # build to bin/upd
nix run .#test           # go test ./... -v -count=1
nix run .#lint           # go vet + go build + golangci-lint
nix run .#run -- <args>  # go run ./cmd/upd <args>
nix flake check          # validate the flake

Plain Go equivalents (requires GOEXPERIMENT=jsonv2):

export GOEXPERIMENT=jsonv2   # required — uses encoding/json/v2
go build ./cmd/upd
go test -race ./...
go vet ./...
Render the demo
nix run .#demo              # render GIF locally to demo/
nix run .#demo -- --publish # render + upload to vhs.charm.sh cloud

Origin

  • Original: rse/upd — an pnpm package written in JavaScript/Node.js by Dr. Ralf S. Engelschall.
  • This project: a complete Go rewrite by Lars Artmann, keeping the same CLI behavior and philosophy while leveraging Go's performance, compile-time type safety, and single-binary distribution.

License

MIT — Copyright © 2015-2026 Dr. Ralf S. Engelschall, Copyright © 2026 Lars Artmann.

See LICENSE for the full text.

Documentation

Overview

Package upd upgrades NPM package dependencies in package.json files while preserving formatting. It is usable both as a library and via the upd command-line tool.

The typical library flow is:

cfg := upd.DefaultConfig()
cfg.File = "package.json"
cfg.Registry = "https://registry.npmjs.org"
cfg.Timeout = 20 * time.Second
cfg.Retries = 3
cfg.Nop = true // dry-run

pkg, err := upd.ReadPackageFile(cfg.File)
if err != nil { return err }

args, err := pkg.GetUpdArgs()
if err != nil { return err }

manifest, warnings := upd.BuildManifest(pkg, args, false)
engine := upd.NewEngine(cfg)
results := engine.FetchAll(ctx, manifest.ToCheck())
updates, errs := engine.ApplyUpdates(manifest, results, pkg)
if updates > 0 && !cfg.Nop {
    _ = pkg.Write(cfg.File)
}

Index

Constants

View Source
const (
	ProgramName = "upd"
	ProgramDesc = "Upgrade NPM Package Dependencies"
	ProgramURL  = "https://github.com/LarsArtmann/upd"
)
View Source
const (
	EnvQuiet       = "UPD_QUIET"
	EnvNop         = "UPD_NOP"
	EnvDryRun      = "UPD_DRY_RUN"
	EnvNoColor     = "UPD_NO_COLOR"
	EnvGreatest    = "UPD_GREATEST"
	EnvAll         = "UPD_ALL"
	EnvPinLatest   = "UPD_PIN_LATEST"
	EnvJSON        = "UPD_JSON"
	EnvVerbose     = "UPD_VERBOSE"
	EnvFile        = "UPD_FILE"
	EnvRegistry    = "UPD_REGISTRY"
	EnvConcurrency = "UPD_CONCURRENCY"
	EnvRetries     = "UPD_RETRIES"
	EnvTimeout     = "UPD_TIMEOUT"
)

Variables

View Source
var (
	ErrHelp    = errors.New("help requested")
	ErrVersion = errors.New("version requested")
)
View Source
var (
	ErrFileNotFound    = errorfamily.NewRejection("file.not_found", "package configuration file not found")
	ErrInvalidJSON     = errorfamily.NewCorruption("json.invalid", "invalid JSON in package configuration file")
	ErrPackageNotFound = errorfamily.NewRejection(
		"registry.package_not_found",
		"package not found in NPM registry",
	)
	ErrRegistryUnavailable = errorfamily.NewTransient("registry.unavailable", "NPM registry is unavailable")
	ErrVersionParse        = errorfamily.NewCorruption("version.parse_failed", "failed to parse semantic version")
	ErrNoLatestDistTag     = errorfamily.NewCorruption("version.no_latest", "no \"latest\" dist-tag found")
	ErrNoValidVersions     = errorfamily.NewCorruption("version.no_versions", "no valid versions found")
	ErrNoSemverVersions    = errorfamily.NewCorruption("version.no_semver", "no valid semver versions found")
	ErrSectionNotFound     = errorfamily.NewRejection(
		"json.section_missing",
		"section not found in package configuration file",
	)
	ErrSectionNotObject   = errorfamily.NewCorruption("json.section_not_object", "section is not a JSON object")
	ErrDependencyNotFound = errorfamily.NewRejection(
		"json.dependency_missing",
		"dependency not found in package configuration file",
	)
	ErrConcurrentModification = errorfamily.NewConflict(
		"file.concurrent_modification",
		"package configuration file was modified concurrently since read",
	)
	ErrPartialFailure = errorfamily.NewRejection(
		"update.partial_failure",
		"one or more dependencies could not be resolved",
	)
)

Domain errors — classified by behavioral family. Rejection = caller's fault (not found, bad input). Exit 1. Transient = temporary, retryable. Exit 75. Corruption = data damaged. Exit 65 (EX_DATAERR). Conflict = state mismatch. Exit 1.

Control-flow signals (ErrHelp, ErrVersion) live in config.go.

View Source
var ProgramVersion = "dev"

ProgramVersion is injected at build time via -ldflags="-X github.com/LarsArtmann/upd.ProgramVersion=1.2.3".

Functions

func RenderJSON

func RenderJSON(w io.Writer, manifest Manifest, updates int) error

RenderJSON writes machine-readable JSON to w. Intended for CI pipelines and editor integrations where the table output is difficult to parse.

func ShouldDisableColor

func ShouldDisableColor(w io.Writer) bool

ShouldDisableColor returns true if ANSI color codes should be suppressed. It honors the NO_COLOR environment variable (https://no-color.org/) and detects non-TTY writers (e.g. piped or redirected output).

Types

type Config

type Config struct {
	File        string
	Registry    string
	Greatest    bool
	All         bool
	Quiet       bool
	Nop         bool
	NoColor     bool
	PinLatest   bool
	JSON        bool
	Verbose     bool
	Concurrency int
	Retries     int
	Timeout     time.Duration
	Patterns    []string
}

func DefaultConfig

func DefaultConfig() *Config

func NewCommand added in v1.2.0

func NewCommand(runE func(context.Context, *Config) error) (*cobra.Command, *Config)

NewCommand builds the root cobra command for upd. The run callback receives the signal-aware context provided by fang and the parsed configuration. The returned Config is the same instance passed to the callback, so callers can inspect it after ParseFlags in tests.

func ParseFlags

func ParseFlags(args []string) (*Config, error)

ParseFlags parses CLI arguments into a Config without executing the command. It is kept for backwards compatibility and for tests. If help or version is requested, it returns ErrHelp or ErrVersion.

func (*Config) UserAgent

func (c *Config) UserAgent() string

func (*Config) Validate added in v1.3.0

func (c *Config) Validate() *Config

Validate clamps zero or negative values to safe defaults so the engine cannot deadlock or hang on misconfigured callers. It mutates the receiver in place and returns it for chaining.

type Engine

type Engine struct {
	// contains filtered or unexported fields
}

func NewEngine

func NewEngine(cfg *Config) *Engine

func (*Engine) ApplyUpdates

func (e *Engine) ApplyUpdates(
	manifest Manifest,
	results map[string]*FetchResult,
	pkg *PackageFile,
) (int, int)

func (*Engine) FetchAll

func (e *Engine) FetchAll(ctx context.Context, names []string) map[string]*FetchResult

func (*Engine) WithReporter

func (e *Engine) WithReporter(r Reporter) *Engine

type FetchResult

type FetchResult struct {
	// contains filtered or unexported fields
}

FetchResult holds the outcome of fetching a single package's packument from the NPM registry. Callers receive a map of these from FetchAll and pass it opaquely to ApplyUpdates.

func (FetchResult) String

func (r FetchResult) String() string

type Manifest

type Manifest map[string][]*Spec

func BuildManifest

func BuildManifest(pkg *PackageFile, patterns []string, pinLatest bool) (Manifest, []string)

func (Manifest) SortedNames

func (m Manifest) SortedNames() []string

func (Manifest) ToCheck

func (m Manifest) ToCheck() []string

type PackageFile

type PackageFile struct {
	// contains filtered or unexported fields
}

func ReadPackageFile

func ReadPackageFile(path string) (*PackageFile, error)

func (*PackageFile) GetDependencySection

func (p *PackageFile) GetDependencySection(section string) (map[string]string, error)

func (*PackageFile) GetUpdArgs

func (p *PackageFile) GetUpdArgs() ([]string, error)

func (*PackageFile) Raw

func (p *PackageFile) Raw() []byte

func (*PackageFile) UpdateDependency

func (p *PackageFile) UpdateDependency(section, name, newValue string) error

func (*PackageFile) Write

func (p *PackageFile) Write(path string) error

type Packument

type Packument struct {
	// contains filtered or unexported fields
}

func (*Packument) GreatestVersion

func (p *Packument) GreatestVersion() (string, error)

func (*Packument) LatestVersion

func (p *Packument) LatestVersion() (string, error)

func (*Packument) VersionKeys

func (p *Packument) VersionKeys() []string

type ProgressReporter

type ProgressReporter struct {
	// contains filtered or unexported fields
}

func NewProgressReporter

func NewProgressReporter(w io.Writer, total int, _ bool) *ProgressReporter

func (*ProgressReporter) Finish

func (p *ProgressReporter) Finish()

func (*ProgressReporter) Start

func (p *ProgressReporter) Start()

func (*ProgressReporter) Tick

func (p *ProgressReporter) Tick(msg string, _ int)

type RegistryClient

type RegistryClient struct {
	// contains filtered or unexported fields
}

func NewRegistryClient

func NewRegistryClient(cfg *Config) *RegistryClient

func (*RegistryClient) FetchPackument

func (c *RegistryClient) FetchPackument(ctx context.Context, name string) (*Packument, int, error)

type Renderer

type Renderer struct {
	// contains filtered or unexported fields
}

func NewRenderer

func NewRenderer(w io.Writer, opts RendererOptions) *Renderer

func (*Renderer) RenderTable

func (r *Renderer) RenderTable(manifest Manifest, updates, errors int, showAll bool)

type RendererOptions

type RendererOptions struct {
	NoColor bool
	Verbose bool
}

type Reporter

type Reporter interface {
	Tick(msg string, bytes int)
}

type Spec

type Spec struct {
	Section  string
	Name     string
	SOld     string
	VOld     string
	SNew     string
	VNew     string
	State    State
	Err      error
	IsLatest bool
}

func (*Spec) String

func (s *Spec) String() string

type State

type State string
const (
	StateTodo    State = "todo"
	StateCheck   State = "check"
	StateSkipped State = "skipped"
	StateKept    State = "kept"
	StateUpdated State = "updated"
	StateError   State = "error"
	StateIgnored State = "ignored"
)

Directories

Path Synopsis
cmd
upd command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL