Metadata-Version: 2.4
Name: upd
Version: 0.8.8
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Rust
Classifier: Topic :: Software Development :: Build Tools
License-File: LICENSE
Summary: Local-first dependency updates for polyglot repositories, including Python, Node.js, Rust, Go, Ruby, .NET, Terraform, GitHub Actions, pre-commit, and Mise
Keywords: dependencies,update,python,nodejs,rust,go,ruby,terraform,github-actions
Home-Page: https://github.com/rvben/upd
Author-email: Ruben Jongejan <ruben.jongejan@gmail.com>
License: MIT
Requires-Python: >=3.8
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Changelog, https://github.com/rvben/upd/blob/main/CHANGELOG.md
Project-URL: Documentation, https://github.com/rvben/upd#readme
Project-URL: Homepage, https://github.com/rvben/upd
Project-URL: Issues, https://github.com/rvben/upd/issues
Project-URL: Repository, https://github.com/rvben/upd
Project-URL: Security, https://github.com/rvben/upd/security/policy

<p align="center">
  <img src="https://raw.githubusercontent.com/rvben/upd/main/assets/logo-wide.svg" alt="upd logo" width="400">
</p>

# upd

<p align="center">
  <strong>Update dependencies across a polyglot repository—locally, safely, and in one command.</strong>
</p>

<p align="center">
  Python · Node.js · Rust · Go · Ruby · .NET · Terraform · GitHub Actions · pre-commit · Mise
</p>

[![crates.io](https://img.shields.io/crates/v/upd.svg)](https://crates.io/crates/upd)
[![PyPI](https://img.shields.io/pypi/v/upd.svg)](https://pypi.org/project/upd/)
[![CI](https://github.com/rvben/upd/actions/workflows/ci.yml/badge.svg)](https://github.com/rvben/upd/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/rvben/upd/blob/main/LICENSE)

`upd` gives you one reviewable update plan across mixed stacks. It preserves
hand-written constraints, comments, and formatting; previews every proposed
change before it writes; and runs without a hosted service or repository
onboarding.

## Try it now

From any directory inside a Git repository:

```bash
uvx upd
```

That first run is a dry run: it reports what would change and leaves every file
untouched. No repository onboarding or hosted account is required. Review the
plan, then apply it with `uvx upd --apply`.

<p align="center">
  <img src="https://raw.githubusercontent.com/rvben/upd/main/assets/terminal-demo.svg" alt="upd previewing six dependency updates across four files without changing them" width="1000">
</p>

<p align="center">
  <sub>One preview across four file types. Nothing changes until you pass <code>--apply</code>.</sub>
</p>

<p align="center">
  <a href="#installation">Install another way</a> ·
  <a href="https://github.com/rvben/upd/blob/main/docs/ecosystems.md">See every supported file</a> ·
  <a href="https://github.com/rvben/upd/blob/main/docs/comparison.md">Compare dependency tools</a>
</p>

## Why upd

- **One command for a mixed stack.** Check application dependencies, tool
  versions, GitHub Actions, pre-commit hooks, and Terraform modules in the same
  run instead of assembling a different updater for each file type.
- **Safe on the first run.** Dry-run is the default, constraints and formatting
  are preserved, and major updates are called out before you decide what to
  apply.
- **Local first, automation ready.** Start with an interactive terminal review,
  then use the same CLI, configuration, stable exit codes, JSON/SARIF output,
  and rolling PR or MR workflows in CI.

`upd` is a local checker and editor, not a package manager or a hosted update
bot. It can delegate lockfile refreshes to the package managers already in your
project and can run inside your own GitHub or GitLab automation. See the
[decision guide and dated benchmarks](https://github.com/rvben/upd/blob/main/docs/comparison.md)
for an exact comparison with adjacent tools.

## Where upd fits

Choose by the job rather than treating every dependency tool as interchangeable:

| If you need to… | Start with… |
|---|---|
| Preview and edit versions across a mixed-language repository from one local command | **upd** |
| Resolve, lock, install, and manage a Python environment | [uv](https://github.com/astral-sh/uv) or [PDM](https://github.com/pdm-project/pdm) |
| Continuously create update branches and PRs across the broadest manager catalog | [Renovate](https://github.com/renovatebot/renovate) or Dependabot |
| Enforce immutable GitHub Actions references as a dedicated policy | [pinact](https://github.com/suzuki-shunsuke/pinact) or [ratchet](https://github.com/sethvargo/ratchet) |

### Dated performance evidence

On the committed 18-reference fixture, the verified 2026-08-28 run recorded:

| Workload | upd mean | Same-workload cohort |
|---|---:|---:|
| Check 12 Python constraints | **134.7 ms** | uppd: 269.7 ms |
| Update 12 Python constraints | **138.7 ms** | uppd: 250.8 ms |
| Check 6 GitHub Actions | **758.7 ms** | taze: 1,019.0 ms; ratchet: 1,029.1 ms |
| Update 6 GitHub Actions | 746.3 ms | **taze: 566.3 ms**; ratchet: 1,066.8 ms |

Lower is better. These are five-run live-network observations, not universal
speed claims. Every timed update was first verified to change all intended
references and leave a parseable result. See the
[full matrix, methodology, variance, commands, and raw JSON](https://github.com/rvben/upd/blob/main/docs/comparison.md).

## Features

- **Multi-ecosystem**: Python, Node.js, Rust, Go, Ruby, .NET, Terraform, GitHub Actions, pre-commit, Mise/asdf
- **Dry-run by default**: nothing is written without `--apply`
- **Fast**: parallel registry requests, with a 24-hour version cache
- **Constraint-aware**: respects `>=2.0,<3` (Python), `~> 7.1` (Ruby), and `^2.0.0` / `~2.0.0` (npm, Cargo)
- **Format-preserving**: keeps formatting, comments, and structure
- **Update filters**: `--only-bump`, `--max-bump`, `--package`, `--lang`, or approve one by one with `-i`
- **Major warnings**: breaking changes are flagged with `(MAJOR)`
- **Pre-release aware**: updates pre-releases to newer pre-releases
- **Cooldown**: hold back releases younger than N days, against supply-chain attacks
- **Security auditing**: OSV vulnerability scanning with auto-fix and SARIF output
- **Check mode**: exit 1 if updates are available (for CI and pre-commit)
- **Gitignore-aware**: honors `.gitignore` and prunes hidden directories, without missing the dotfiles it updates
- **Private registries**: authentication for PyPI, npm, Cargo, Go, and GitHub
- **Config file**: include or exclude paths, ignore packages, and pin versions via `.updrc.toml`

## Installation

### From crates.io

```bash
cargo install upd

# or with cargo-binstall (faster, pre-built binary)
cargo binstall upd
```

### From PyPI

```bash
pip install upd
# or with uv
uv pip install upd
```

If you installed an earlier release under the old distribution name, migrate
once with `pip uninstall upd-cli && pip install upd`. The `upd-cli` command
remains available as a compatibility alias.

### From source

```bash
git clone https://github.com/rvben/upd
cd upd
cargo install --path .
```

## Usage

```bash
# Preview changes without modifying files (default when no --apply)
upd

# Apply updates to files
upd --apply

# Limit to specific files or directories
upd --apply requirements.txt pyproject.toml

# Approve updates one by one
upd -i

# Only the packages you name
upd -p requests,flask

# Cap the bump level (allow patch + minor, skip major). Updates above the
# ceiling are reported as held back, never as up to date, and do not
# change the exit code.
upd --max-bump minor

# Restrict to exactly one level (repeatable, comma-separated)
upd --only-bump major

# One ecosystem at a time: python, node, rust, go, ruby, dot-net,
# terraform, actions, pre-commit, mise, annotated
upd --lang python

# Exit 1 if anything is outdated (for CI and pre-commit)
upd --check

# Regenerate lockfiles after writing
upd --apply --lock

# Print the effective configuration and exit
upd --show-config
```

`upd --help` lists every flag; [Stability](https://github.com/rvben/upd/blob/main/docs/stability.md)
documents the ones that are contractual, and `upd schema` emits the whole
interface as JSON.

> **Dry-run by default**: `upd` without `--apply` only previews changes. Pass `--apply` to
> write updates. `--check`, `--dry-run`, and `--interactive` do not require `--apply`.
>
> **VCS-root scoping**: When no path argument is given, `upd` scans from the nearest `.git`
> ancestor directory rather than the current working directory. This prevents accidental
> rewrites when CWD is a subdirectory inside a repository.

### Commands

```bash
upd --version      # Print version
upd self-update    # Check for upd updates
upd clean-cache    # Clear the version cache
upd align          # Align versions across files (--check exits 1 on misalignment)
upd audit          # Scan for known vulnerabilities (exit 6 if found)
upd schema         # Machine-readable interface description
```

For repositories replacing dependency bots, `upd` also provides separate
reusable GitHub workflows for policy-constrained freshness updates and
validated security-remediation pull requests. See
[GitHub dependency pull requests](docs/github-actions.md).

## Example Output

```text
.pre-commit-config.yaml:37: Would update pre-commit/pre-commit-hooks v4.6.0 → v6.0.0 (MAJOR)
.github/workflows/ci.yml:16: Would update actions/checkout v4 → v6 (MAJOR)
.github/workflows/ci.yml:18: Would update jdx/mise-action v2 → v4 (MAJOR)
.mise.toml:8: Would update rust 1.91.1 → 1.94.0
Cargo.toml:33: Would update clap 4.5.53 → 4.6.0
Cargo.toml:36: Would update tokio 1.48.0 → 1.50.0

Would update 6 package(s) (2 major, 3 minor, 1 patch) in 4 file(s), 8 up to date
```

Output includes clickable `file:line:` locations (recognized by VS Code, iTerm2, and modern terminals).

## Version Constraints

`upd` respects version constraints in your dependency files:

| Constraint | Behavior |
|------------|----------|
| `>=2.0,<3` | Updates within 2.x range only |
| `^2.0.0` | Updates within 2.x range (npm/Cargo); never crosses the major bound |
| `~2.0.0` | Updates within 2.0.x range (npm); `~2.0.0` (Cargo) stays within 2.0.x |
| `~> 7.1` | Updates within 7.x range (Ruby pessimistic) |
| `>=2.0` | Updates to any version >= 2.0 |
| `==2.0.0` | Updates the exact pin to the latest version (e.g. `==2.0.0` → `==3.1.5`). To freeze a package, use `[pin]` or `ignore` in `.updrc.toml`. |

An update moves the **lower bound** and leaves every other clause where the
author wrote it, so `>=1.0, <2.0` becomes `>=1.5.0, <2.0`. A constraint is an
unordered set of clauses, so the lower bound is found wherever it sits
(`<2.0, >=1.0` answers alike), and an upper bound is honored when picking the
new version: the release chosen is the newest one the constraint already admits.

npm ranges keep the shape they were written in. A comparator range
(`">=1.0.0 <2.0.0"`) and a hyphen range (`"4.17.0 - 4.18.0"`) each keep their
ceiling. A wildcard or partial range takes its ceiling from its own floor, like
a caret, so it follows the newest release and the whole shape moves with it:
`"4.3.x"` becomes `"4.4.x"` and `"^1.2"` becomes `"^3.1"`, never a fully
written version. npm lets a comparator stand apart from the version it applies
to, and that spacing is part of the shape: `">= 1.2.7 < 1.3.0"` is read as the
range it is and comes back spaced the same way. npm's tilde has two spellings
and `"~>1.2.3"` means what `"~1.2.3"` does, ceiling included; each comes back
spelled the way it was written.

An npm spec that names no published version is left alone and reported nowhere:
`"*"`, a dist-tag (`"latest"`, `"next"`, `"beta"`), and the `workspace:`,
`file:`, `link:`, `npm:`, `git+ssh:` and `github:owner/repo` forms all resolve
somewhere other than a release on the registry, so there is no version to
compare and nothing an update could move.

### Bounds that are not floors

Only an **inclusive** lower bound names the version a project is on, so only
that bound is raised. `>1.2.3` names the one version its author refuses, `<3`
and `<=3` are ceilings, `!= 1.5` is an exclusion, and an OR range
(`"^1 || ^2"`) has no single branch to edit. None of them is a floor, so none
of them is moved. They are checked against the registry and reported anyway:

| Outcome | Reported as |
|---------|-------------|
| The constraint admits the newest release | Up to date |
| The newest release has outgrown it | A warning naming the release and the constraint |
| The spec cannot be read at all | An error, exit `2` |

The last row is the point of the other two: a dependency nothing looked at must
not be counted as up to date, and a constraint that has quietly frozen a
dependency should say so rather than pass under a green tick.

## Annotated Version Pins

Files without a dependency-manifest format can carry a trailing annotation:

```yaml
shinyhub_version: "0.11.16"  # upd: pypi shinyhub
```

Directory walks scan annotations in `Makefile`, `makefile`, `GNUmakefile`,
`justfile`, `Justfile`, `*.mk`, `*.sh`, and `*.bash`. Any file passed explicitly
is scanned as annotated. To add other files to normal repository discovery, use
repository-relative globs in `.updrc.toml`:

```toml
include = ["ansible/roles/*/vars/*.yml", "docker-compose.yml"]
exclude = ["**/archive/**"] # exclude wins over include
```

An include never changes a recognized manifest's parser: for example, a
matching `main.tf` remains Terraform. Use `--verbose` to diagnose an `upd:`
marker in an otherwise undiscovered UTF-8 text file up to 1 MiB.

A GitHub Actions workflow is the exception: it keeps its Actions updater and is
scanned for annotations as well, so a tool version passed to an action through a
`with:` input can be updated beside the `uses:` refs around it. See
[GitHub Actions](docs/github-actions.md#annotated-versions-in-a-workflow).

## Version Precision

By default, `upd` preserves version precision from the original file:

```text
# Original file has 2-component versions
flask>=2.0        →  flask>=3.1        (not 3.1.5)
django>=4         →  django>=6         (not 6.0.0)

# Original file has 3-component versions
requests>=2.0.0   →  requests>=2.32.5

# GitHub Actions major-only tags
actions/checkout@v3  →  actions/checkout@v4  (not @v4.2.0)
```

Use `--full-precision` to always output full semver versions:

```text
upd --full-precision
flask>=2.0        →  flask>=3.1.5
django>=4         →  django>=6.0.0
requests>=2.0.0   →  requests>=2.32.5
```

## Version Alignment

In monorepos or projects with multiple dependency files, the same package might
have different versions:

```text
# requirements.txt
requests==2.28.0

# requirements-dev.txt
requests==2.31.0

# services/api/requirements.txt
requests==2.25.0
```

`upd align` updates every occurrence to the highest version found:

```bash
upd align              # Align all packages to highest version
upd align --dry-run    # Preview changes
upd align --check      # Exit 1 if misalignments (for CI)
upd align --lang python # Align only Python packages
```

It only aligns within one ecosystem, skips packages with upper bound
constraints (e.g. `>=2.0,<3.0`) to avoid breaking them, and ignores
pre-release versions when finding the highest version.

## Pre-commit Integration

Add `upd` to your `.pre-commit-config.yaml`:

```yaml
repos:
  - repo: https://github.com/rvben/upd-pre-commit
    rev: v0.0.24
    hooks:
      - id: upd-check
        # Optional: only check specific ecosystems
        # args: ['--lang', 'python']
```

Available hooks:

| Hook ID | Description |
|---------|-------------|
| `upd-check` | Fail if any dependencies are outdated |
| `upd-check-major` | Fail only on major (breaking) updates |

Both hooks run on `pre-push` by default. Uses `language: python` which installs `upd` from PyPI automatically, so no manual installation is needed.

## Documentation

Everything you look up rather than read lives in
[docs/](https://github.com/rvben/upd/tree/main/docs).

### Releases

Vership workflow, publication guarantees, automated integration pins, and safe
retry procedures.
→ [docs/releases.md](https://github.com/rvben/upd/blob/main/docs/releases.md)

### Supported files

Every file `upd` discovers, per ecosystem, plus annotated version pins in files
it does not otherwise understand.
→ [docs/ecosystems.md](https://github.com/rvben/upd/blob/main/docs/ecosystems.md)

### Comparison and benchmarks

A dated feature matrix for related dependency tools, plus workload-based,
reproducible benchmarks that avoid ranking unlike operations.
→ [docs/comparison.md](https://github.com/rvben/upd/blob/main/docs/comparison.md)

### Security auditing

OSV vulnerability scanning, `--fix-audit`, SARIF output, and CI integration.
→ [docs/audit.md](https://github.com/rvben/upd/blob/main/docs/audit.md)

### Security policy

Private vulnerability reporting, supported versions, trust boundaries, and
release integrity.
→ [SECURITY.md](https://github.com/rvben/upd/blob/main/SECURITY.md)

### Configuration file

`.updrc.toml` discovery order and every key it accepts.
→ [docs/configuration.md](https://github.com/rvben/upd/blob/main/docs/configuration.md)

### Cooldown (minimum release age)

Hold back versions published less than N days ago, per ecosystem.
→ [docs/configuration.md#cooldown-minimum-release-age](https://github.com/rvben/upd/blob/main/docs/configuration.md#cooldown-minimum-release-age)

### Caching

Where the 24-hour version cache lives and how to clear or bypass it.
→ [docs/configuration.md#caching](https://github.com/rvben/upd/blob/main/docs/configuration.md#caching)

### Environment variables

Every variable `upd` reads, in one table.
→ [docs/configuration.md#environment-variables](https://github.com/rvben/upd/blob/main/docs/configuration.md#environment-variables)

### Private repositories

Credential detection for PyPI, npm, Cargo, Go, and GitHub, including private
indexes declared in `pyproject.toml`.
→ [docs/private-registries.md](https://github.com/rvben/upd/blob/main/docs/private-registries.md)

### GitHub pull requests

Run any supported dependency updates as one rolling GitHub PR, with immutable
Action SHA verification, validation, artifact reporting, and opt-in auto-merge.
→ [docs/github-actions.md](https://github.com/rvben/upd/blob/main/docs/github-actions.md)

### GitLab merge requests

Run scheduled dependency updates as one rolling GitLab MR, with validation,
lease-protected branch updates, and explicitly opt-in GitLab-native auto-merge.
→ [docs/gitlab.md](https://github.com/rvben/upd/blob/main/docs/gitlab.md)

### Stability

The stable CLI surface, exit codes, `--lock` commands, and output guarantees.
→ [docs/stability.md](https://github.com/rvben/upd/blob/main/docs/stability.md)

## Development

```bash
# Build
make build

# Run tests
make test

# Lint
make lint

# Format
make fmt

# All checks
make check
```

## License

MIT

