Central, module-based repo updater that detects a repo's runtime + package manager and bumps everything: Node LTS,
@types/node, all dependencies, the package manager itself, GitHub Actions pins, and Node
versions in Docker/Compose files.
# run bumper in the current repo, auto-detecting everything
npx @enke.dev/bumper update # with npm
pnpm dlx @enke.dev/bumper update # with pnpm
bunx --bun @enke.dev/bumper update # with Bun, no Node neededWorks across node/pnpm, node/npm and bun repos, single-package or monorepo.
Dependencies are bumped by resolving each package's latest version via the repo's own package
manager (pnpm view / npm view, so private scoped registries + .npmrc auth just work),
rewriting package.json specs (preserving ^/~), then letting the package manager reinstall.
Bumps are peer-aware: before rewriting, bumper reads the peerDependencies of every direct
dependency — for the version it's about to bump to, resolved from the registry rather than the
stale one in node_modules — and caps each shared dependency to the newest version still
satisfying every declared peer range. So a preset that peer-pins typescript to 6.0.3
(e.g. @enke.dev/lint) holds typescript at
6.0.3 instead of jumping to a newer major that would break the preset — no per-repo config
needed, the declaration lives in the dependency itself. Because the peers are read for the target
version, a peer newly introduced (or changed) by that bump is honored in the same run — no
second pass needed to converge.
Note — network runs through subprocesses (
curlfor the Node dist index,pnpm/npm viewfor versions,actions-upviabunx), so private-registry auth is handled by the tools that own it — the repo's own.npmrcjust works.
Published publicly to npm as @enke.dev/bumper.
The bin is a single bundled JS file (dist/cli.mjs) — no platform binaries — so it runs on
Node ≥22 or Bun.
Use your package manager's runner to fetch + execute the latest version on the fly:
npx @enke.dev/bumper … # npm
pnpm dlx @enke.dev/bumper … # pnpm
bunx --bun @enke.dev/bumper … # bun (--bun forces the Bun runtime, no Node needed)npm install -g @enke.dev/bumper
pnpm add -g @enke.dev/bumper
bun add -g @enke.dev/bumperThen invoke bumper (or bmpr) directly.
Requires Bun to build.
bun install # installs deps + builds ./dist/cli.mjs (prepare hook)
bun run dev … # run the CLI straight from source, no build neededTo get a global bumper on your PATH from the working tree (e.g. to dogfood it in other
repos), build and link once:
bun run build # refresh ./dist/cli.mjs
bun link # symlinks it into ~/.bun/bin (on PATH for a standard Bun install)The link tracks dist/cli.mjs live — re-run bun run build to update what bumper executes;
no need to link again. bun link writes the platform-appropriate shim, so this works on Windows too.
bumper # no command → shows help
bumper help # show help (also: bumper --help)
bumper detect # show context + applicable modules for the cwd
bumper detect /path --json # machine-readable detection
bumper update # run every applicable module, in order
bumper update /path/to/repo # target another repo (defaults to cwd)Every invocation prints a one-line bumper v<version> banner (suppressed under --json). During
update, bumper also checks the registry for a newer release of itself — the lookup runs
concurrently with the module work, so it adds no perceptible latency — and, if one exists, prints a
hint with the install command afterwards. The check is silent when offline/unresolvable. Skip it
for a single run with --skip-update-check, or disable it globally by setting skipVersionCheck
in ~/.bumperrc (top level, next to repos).
All flags apply to bumper update; --json is detect-only and --ignore-config applies to
both. Repeatable flags are given several times — one value each, no comma-separated lists.
| Flag | Repeatable | What it does |
|---|---|---|
--dry-run |
no | Print every intended step, change nothing on disk. |
--commit, -c |
no | After updating, commit the changes as chore: update dependencies with a summary. |
--only <id> |
yes | Run only the named module(s); everything else is skipped. |
--skip <id> |
yes | Run everything except the named module(s). |
--exclude, -e <path> |
yes | Skip a repo-relative path this run only, without editing config (see Excludes). |
--ignore-config |
no | Ignore ~/.bumperrc for this run — auto-detect everything, read + write nothing. |
--json |
no | detect only — emit machine-readable detection output. |
--only and --skip take module ids from the Modules table (node, types-node,
bun, npm, pnpm, docker, github-actions).
bumper update --dry-run # preview, no writes
bumper update --only node,pnpm # just the Node runtime + pnpm modules
bumper update --skip github-actions # everything but the actions pinner
bumper update --exclude examples # skip a path this run, without editing config
bumper update --exclude examples --exclude fixtures # repeat the flag for several
bumper update --ignore-config # ignore stored excludes/toggles, pure auto-detect
bumper update --commit # update, then commit with a summaryWith --commit (-c), bumper stages everything and commits as chore: update dependencies once
the run finishes, with a markdown body grouping what changed — dependency specs (old → new), the
packageManager field, the Node/Bun version pins, and GitHub Action pins, with any other touched
files listed by path. The changes are read back from git after the run, so the summary reflects
exactly what landed. Skipped (with a note) when the target isn't a git repo, when nothing changed,
or under --dry-run.
--ignore-config bypasses ~/.bumperrc completely: no entry is read for the target repo and, for
an unknown repo, none is written. Stored excludes and module toggles are skipped — use it to run
exactly what auto-detection finds, or to preview a repo without persisting a default entry.
Each concern is a self-detecting, self-updating unit behind a common Module interface, in one of
three families (mirrored by the modules/ folder layout): runtimes, package-managers and
features. The update procedure just runs every module whose detector matches, in registry order
— runtimes first (pin versions), then dependency-pinning features, then package managers install,
then the remaining file-rewriting features:
| id | kind | detects | does |
|---|---|---|---|
node |
runtime | node runtime / .node-version |
install latest LTS via fnm/asdf, write .node-version |
types-node |
feature | @types/node in any package |
pin spec to exact latest in the Node LTS major line |
bun |
package-manager | bun packageManager / lockfile | self-upgrade, bump specs, pin .bun-version, reinstall |
npm |
package-manager | npm packageManager / lockfile | bump specs to latest, clean reinstall, approve-scripts --all |
pnpm |
package-manager | pnpm packageManager / lockfile | self-update, bump specs to latest, clean reinstall, approve-builds --all |
docker |
feature | Dockerfile* / compose*.yaml |
align node:<ver> / NODE_VERSION= to LTS |
github-actions |
feature | .github/workflows/*.y{a,}ml |
pin actions via actions-up |
Adding a concern = adding one module (*.runtime.ts / *.package-manager.ts / *.feature.ts)
implementing the Module interface and registering it. bumper detect exposes per-module
detection, so a later multi-step CLI or GUI can build on top of the same registry.
The composite action at the repo root runs bumper update --commit, pushes the result to a
dedicated branch, and opens (or updates) a pull request — no local installation needed. Drop it
into a scheduled workflow in any target repo.
Create .github/workflows/update-dependencies.yml in your repo:
name: update dependencies
on:
schedule:
- cron: '0 6 * * 1' # every Monday at 06:00 UTC
workflow_dispatch: # also allow manual runs
jobs:
update-dependencies:
runs-on: ubuntu-latest
permissions:
contents: write # push the update branch
pull-requests: write # open the PR
steps:
- uses: enke-dev/bumper@main
with:
base: mainThe job needs contents: write (to push the update branch) and pull-requests: write (to open
the PR), as shown above.
Prerequisite — GitHub disables Actions-authored PRs by default. Enable Settings → Actions → General → Workflow permissions → "Allow GitHub Actions to create and approve pull requests" (also settable org-wide). Without it the branch is still pushed, but PR creation fails with
GitHub Actions is not permitted to create or approve pull requests. Alternatively, pass a PAT or GitHub App token via thetokeninput, which bypasses the setting and lets the resulting PR trigger your otherpull_requestworkflows (a bot's defaultGITHUB_TOKENwon't).
All inputs are optional.
| Input | Default | Description |
|---|---|---|
base |
repository default branch | Base branch for the PR. |
branch |
chore/bumper-update |
Branch name used for the update commit and PR. |
pr-title |
chore: update dependencies |
Title of the created or updated PR. |
pr-labels |
(none) | Comma-separated labels to apply to the PR (labels must already exist in the repo). |
only |
(all modules) | Run only the listed module ids (comma-separated, e.g. node,pnpm). |
skip |
(none) | Skip the listed module ids (comma-separated, e.g. docker). |
exclude |
(none) | Space-separated repo-relative paths to exclude (e.g. examples fixtures). |
token |
${{ github.token }} |
Token used to push the branch and open the PR (pass a PAT/app token to have the PR trigger other workflows). |
Module ids are the values from the id column in the Modules table.
| Output | Description |
|---|---|
updated |
"true" when bumper produced a new commit, otherwise "false". |
branch |
The update branch name. |
base |
The resolved base branch. |
pr-number |
The created or updated PR number (empty when nothing changed). |
Because it's a step-level action, add your own steps in the same job — they run after the PR is created. Use the outputs to gate them:
jobs:
update-dependencies:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- id: bump
uses: enke-dev/bumper@main
with:
base: main
- if: steps.bump.outputs.updated == 'true'
run: echo "Opened PR #${{ steps.bump.outputs.pr-number }}"name: update dependencies
on:
schedule:
- cron: '0 6 * * 1'
workflow_dispatch:
jobs:
update-dependencies:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: enke-dev/bumper@main
with:
base: main
branch: chore/bumper-update
pr-title: 'chore: update dependencies'
pr-labels: 'dependencies,automated'
skip: docker
exclude: examples fixturesPath-scoped overrides. Running in an unknown repo auto-detects everything and persists a default entry, so the next run is already scoped:
A stored modules toggle is authoritative: docker: false disables that module even where its
files exist, true forces it on. Modules with no entry fall back to auto-detection.
bumper config list
bumper config get # current repo (path defaults to cwd)
bumper config get /path/to/repo # another repo
bumper config set exclude packages/a packages/b # current repo
bumper config set /path/to/repo modules.docker falseget and set default the path to the current repo — omit it to configure where you're standing.
For set a leading config key (exclude, modules.<id>) is what signals the path was omitted.
bumper detect marks anything the config drives — a forced module shows (config: on|off), a
stored exclude shows (from config) — with a footer pointing at the config set to change it and
--ignore-config to bypass. Run bumper detect --ignore-config to see what pure auto-detection
would do.
exclude is a list of repo-relative paths (an exact dir/file or any descendant) that bumper
leaves alone. It applies uniformly:
- workspace members under an excluded path are dropped from every workspace operation, and
- every file-discovering module (e.g.
docker, which globs**/Dockerfile*) skips matches under an excluded path — so vendored packages, fixtures, or example projects used for testing are never rewritten.
Persist it with config set … exclude (space-separated paths, replacing the stored list), or pass
--exclude <path> on a single update run to add paths for that run only (repeat the flag for
several, merged with the stored list, not saved). The common case: a repo whose own examples/ are
self-applied test fixtures —
bumper config set /path/to/repo exclude examples # always skip
bumper update --exclude examples # skip just this runModules that read a single fixed file at the repo root (
github-actions, the package managers) are unaffected —excludetargets subpaths, and the root is never excluded.
{ "skipVersionCheck": true, // global: silence the update self-version check (default: check) "repos": { "/absolute/path/to/repository": { "exclude": ["packages/vendored-pkg"], // repo-relative paths skipped everywhere (see below) "modules": { "docker": false }, // explicit per-module on/off overrides, keyed by module id }, }, }