✨ Stop hunting for every place a version number hides. Point version-manager at your project once, then bump every occurrence — Xcode project, Info.plist, Package.swift, fastlane config, README badges, CHANGELOG — with a single command and a safety net that refuses to run when something looks off.
Every non-trivial project ends up with its version number scattered across files that
never agree with each other for long: MARKETING_VERSION in project.pbxproj, a
badge in README.md, a constant in Version.swift, a Deliverfile for fastlane. A
manual bump means opening five files and hoping you didn't miss one. version-manager
turns that into a single declarative config and one command.
- 🎯 One config, every file. Describe each version location once in
.appversion.ymlwith a regex + capture group — the tool finds and replaces only the version part, never touching the surrounding text. - 🔍 Plan-then-apply, always. Every
bumpbuilds the full change set in memory, validates it, and shows you a diff before writing anything.--dry-runstops right there. - 🚨 Refuses to run silently wrong. Zero matches, a mismatched occurrence count, or rules that disagree on the current version — all hard errors by default. No more "the bump ran but only touched 2 of 3 files."
- 🔤 File renaming, not just content. Version-encoded filenames
(
Configs/1-17-2.xcconfig) get renamed via a small shell transform you control. - 🪝 Pre/post hooks. Run a script before the bump (e.g. require a clean worktree) or after (e.g. insert a CHANGELOG entry), with the old/new version passed in as environment variables.
- 🤖 Agent-friendly by design.
--jsonon every command, no interactive prompts, predictable non-zero exit codes — built to be driven by CI or an AI coding agent as comfortably as by a human. - 📐 Independent SemVer 2.0.0 implementation. No dependency black box — parsing and precedence comparison are implemented and tested directly against the spec.
curl -fsSL https://raw.githubusercontent.com/Ryu0118/version-manager/main/install.sh | bashTo update, run the same command. It skips the download if already up-to-date.
# Install a specific version
curl -fsSL https://raw.githubusercontent.com/Ryu0118/version-manager/main/install.sh | VERSION=0.1.0 bash
# Force reinstall
curl -fsSL https://raw.githubusercontent.com/Ryu0118/version-manager/main/install.sh | FORCE=1 bashNest (mtj0928/nest)
nest install Ryu0118/version-managerMise (jdx/mise)
mise use -g github:Ryu0118/version-managerRequires Swift 6.2+ and macOS 15+.
git clone https://github.com/Ryu0118/version-manager.git
cd version-manager
swift run version-manager <subcommand># 1. Scaffold a config
version-manager init
# 2. Edit .appversion.yml to point at your version-carrying files (see below)
# 3. Preview a bump — nothing is written yet
version-manager bump --dry-run 1.18.0
# 4. Apply it for real
version-manager bump 1.18.0
# 5. Verify everything stayed in sync (great as a CI gate on release PRs)
version-manager check.appversion.yml lives at your project root. The minimum viable config is a version
string (the single source of truth — current and check read it directly, they never
regex-extract a version from a file) and one files rule:
version: "1.0.0"
files:
- id: version-swift
path: Sources/MyToolCLI/Version.swift
pattern: 'static let current = "(\d+\.\d+\.\d+)"'
occurrences: 1Every pattern must contain exactly one capture group — only the captured text is
replaced, so the surrounding text (static let current = "...") acts as a guard
against accidental matches. occurrences defaults to all (at least one match
required); set an exact integer when you know precisely how many times a version
should appear — a mismatch means something drifted.
A more complete example, covering an Xcode project with multiple targets, a fastlane deliverfile, a README badge, a version-encoded config filename, and pre/post hooks:
version: "1.17.2"
files:
- id: xcodeproj
path: "App/*.xcodeproj/project.pbxproj"
pattern: 'MARKETING_VERSION = (\d+\.\d+\.\d+);'
occurrences: all # Debug/Release × iOS/watchOS/Widget all match
- id: fastlane-deliverfile
path: fastlane/Deliverfile
pattern: 'app_version\("(\d+\.\d+\.\d+)"\)'
occurrences: 1
- id: readme-badge
path: README.md
pattern: 'img\.shields\.io/badge/version-(\d+\.\d+\.\d+)-blue'
occurrences: 1
renames:
- id: version-xcconfig
directory: App/Configs
format: "Version-{version}.xcconfig"
transform:
run: "echo \"$APPVERSION_VALUE\" | tr '.' '-'" # Version-1-17-2.xcconfig -> Version-1-18-0.xcconfig
hooks:
pre:
- name: clean-worktree
run: "git diff --quiet"
post:
- name: insert-changelog-entry
run: "./scripts/insert-changelog-entry.sh"strict (default true) rejects pre-release/build-metadata suffixes like
1.18.0-beta.1 in version — set it to false at the top level to allow them.
version-manager is semver-only; there is no support for non-semver version schemes.
Full schema reference: version-manager install-skills installs a
version-manager-config-guide Agent Skill with the complete field-by-field
documentation, or read skills/version-manager-config-guide/SKILL.md
directly.
version-manager bump <version> [--dry-run] [--json] [--skip-hooks] [--force] [--config <path>] [--verbose]
version-manager check [--json] [--config <path>] [--verbose]
version-manager current [--json] [--config <path>] [--verbose]
version-manager init [--force] [--config <path>] [--verbose]
version-manager install-skills [--agent claude-code|agents|both] [--global | --dir <path>] [--force] [--json]| Command | What it does |
|---|---|
bump <version> |
Plan, validate, then apply a version bump across every configured file, rename, and hook. |
check |
Verify the current repo state is internally consistent — every rule matches, every extracted version agrees with the config's version field. Non-zero exit on drift. Ideal as a CI gate before merging a release PR. |
current |
Print the current version, read directly from the config's version field. |
init |
Write a commented .appversion.yml template to get started. |
install-skills |
Install version-manager's own Agent Skills (config authoring guide + CLI usage guide) into a project (default) or, with --global, into ~/.claude/skills and/or ~/.agents/skills. Prints the full path of every file it writes. |
--config <path> selects a config file other than .appversion.yml (default);
--verbose turns on detailed logging. Both are available on every subcommand except
install-skills. install-skills' own --dir <path> (target project root, default .)
and --global (install into $HOME instead) are mutually exclusive.
--dry-run builds and validates the full change set — every regex match, every
occurrence count, every rename — without writing anything, then prints the diff.
Nothing about bump's validation is skipped in dry-run mode; only the write and any
hooks are.
- Never a silent no-op. A rule that matches zero times, or an occurrence count that doesn't add up, is a hard error — not a warning buried in output you might not read.
- Plan-then-apply. The entire change set — every file replacement, every rename, every hook — is computed and validated in memory before a single byte is written to disk.
- Atomic, best-effort-recoverable writes. Files are written via a temp-file-then-
rename sequence; if a write fails partway through a multi-file bump, already-applied
changes are rolled back on a best-effort basis, with
git checkout -- <files>always available as the ultimate fallback. - Hooks are honest about their limits. A
prehook failure aborts before anything is written. Aposthook failure is reported but does not roll back file changes — hooks may run non-idempotent external actions (like appending to a CHANGELOG) that shouldn't be undone just because a later hook failed.
MIT