Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/smoke-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ jobs:
export HOME=$(mktemp -d)
unset ZDOTDIR
export SHELL=/bin/zsh
unset UV_EXCLUDE_NEWER YARN_NPM_MINIMAL_AGE_GATE COOLDOWN_MINUTES PIP_UPLOADED_PRIOR_TO 2>/dev/null || true
unset UV_EXCLUDE_NEWER YARN_NPM_MINIMAL_AGE_GATE COOLDOWN_MINUTES PIP_UPLOADED_PRIOR_TO BUNDLE_COOLDOWN 2>/dev/null || true
trap 'rm -rf "$HOME"' EXIT
bash -s "$HOME/.zshrc" < "$script"
)
Expand Down
78 changes: 63 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,52 @@ export COOLDOWN_MINUTES=4320 # 3 days, in minutes
cargo cooldown build
```

## Ruby Ecosystem

### Bundler

[Bundler](https://bundler.io/) introduced a native cooldown feature in version 4.0.13. The cooldown value is always a
non-negative integer number of days (a string, float, negative number, or array is rejected). For example, to apply a
three-day cooldown to a single command:

```bash
bundle install --cooldown 3
```

The `--cooldown` flag is supported by the `install`, `update`, `add`, and `outdated` commands.

To set it for the current project, execute:

```bash
bundle config set cooldown 3
```

Or globally for all projects:

```bash
bundle config set --global cooldown 3
```

You can also declare a cooldown per-source directly in the `Gemfile`:

```ruby
source "https://rubygems.org", cooldown: 3
```

Or use the following environment variable:

```bash
export BUNDLE_COOLDOWN=3
```

When multiple settings apply, the override hierarchy is: command-line flag > configuration setting > per-source
`Gemfile` declaration. Passing `0` disables the cooldown for that run.

Bundler fails open: it only holds back versions it can prove are too new. Versions lacking a `created_at` timestamp
(older servers, v1-format registries, some private gems) remain resolvable. See the
[RubyGems blog announcement](https://blog.rubygems.org/2026/06/03/cooldown-let-new-gems-be-vetted.html) for more
information.

## Scala / JVM Ecosystem

### Scala Steward
Expand Down Expand Up @@ -413,16 +459,15 @@ These language ecosystems currently offer no native cooldown support. There's
an [open proposal](https://github.com/golang/go/issues/76485) for Go, but it hasn't
been accepted. [NuGet](https://github.com/NuGet/Home/issues/14657),
[Composer](https://github.com/composer/composer/issues/12633), and
[Hex](https://github.com/hexpm/hex/issues/1113) also have open feature requests. Your best bet is
[Hex](https://github.com/hexpm/hex/issues/1113) also have open feature requests. Swift Package Manager doesn't have
native cooldowns either, and no open request exists requesting this feature as of today. Your best bet is
locking your dependencies to exact versions, and configuring cooldowns in Dependabot or Renovate for automated updates
(see below).

Maven/Gradle (Java) don't have native cooldowns either, but the third-party [Scala Steward](#scala-jvm-ecosystem) bot
described above can apply cooldowns to Maven/Gradle projects (though it's not heavily used outside of Scala).
RubyGems/Bundler (Ruby) and Swift Package Manager don't have native cooldowns either, and no open requests exist
requesting this feature as of today.

One exception worth noting: the community-run [gem.coop package index](https://gem.coop), an alternative to RubyGems,
One related note: the community-run [gem.coop package index](https://gem.coop), an alternative to RubyGems,
enforces a 48-hour delay on newly published gems at the registry level.

## Dependency update bots
Expand Down Expand Up @@ -536,16 +581,18 @@ cooldowns.sh set npm 7d
Each `set` command writes a user-wide configuration for that tool. Project-level configs are not modified. The exact
location depends on the tool:

| Tool | Method | Location |
|-------|--------------------------------------------------|-------------------------------------------------|
| pip | Env var export (26.1+) or shell wrapper (older) | `/etc/profile.d/cooldowns.sh` (or `~/.bashrc`) |
| uv | Env var export | `/etc/profile.d/cooldowns.sh` (or `~/.bashrc`) |
| npm | `.npmrc` key | `~/.npmrc` |
| pnpm | `.npmrc` key | `~/.npmrc` |
| yarn | Env var export | `/etc/profile.d/cooldowns.sh` (or `~/.bashrc`) |
| bun | `bunfig.toml` key | `~/.bunfig.toml` |
| deno | Shell aliases | `/etc/profile.d/cooldowns.sh` (or `~/.bashrc`) |
| cargo | Env var export (requires `cargo-cooldown` crate) | `/etc/profile.d/cooldowns.sh` (or `~/.bashrc`) |
| Tool | Method | Location |
|---------|--------------------------------------------------|------------------------------------------------|
| pip | Env var export (26.1+) or shell wrapper (older) | `/etc/profile.d/cooldowns.sh` (or `~/.bashrc`) |
| uv | Env var export | `/etc/profile.d/cooldowns.sh` (or `~/.bashrc`) |
| poetry | `poetry config` setting | `~/.config/pypoetry/config.toml` |
| npm | `.npmrc` key | `~/.npmrc` |
| pnpm | `.npmrc` key | `~/.npmrc` |
| yarn | Env var export | `/etc/profile.d/cooldowns.sh` (or `~/.bashrc`) |
| bun | `bunfig.toml` key | `~/.bunfig.toml` |
| deno | Shell aliases | `/etc/profile.d/cooldowns.sh` (or `~/.bashrc`) |
| cargo | Env var export (requires `cargo-cooldown` crate) | `/etc/profile.d/cooldowns.sh` (or `~/.bashrc`) |
| bundler | Env var export (requires Bundler >= 4.0.13) | `/etc/profile.d/cooldowns.sh` (or `~/.bashrc`) |

Tools that use profile scripts write to `/etc/profile.d/cooldowns.sh` if the directory exists and is writable,
otherwise they fall back to `~/.bashrc`.
Expand Down Expand Up @@ -603,13 +650,13 @@ RUN cooldowns.sh check
| Bun | Relative durations | `minimumReleaseAge = 259200` in `bunfig.toml` |
| Deno | Relative durations | `minimumDependencyAge: "P3D"` in `deno.json` |
| Cargo | Third-party only | `cargo cooldown <cmd>` via `cargo-cooldown` crate |
| Bundler | Relative durations (4.0.13+) | `bundle config set cooldown 3` / `--cooldown 3` |
| Scala Steward | Relative durations (0.38.0+) | `updates.cooldown.minimumAge = "3 days"` in `.scala-steward.conf` |
| VS Code | Not available | Pin dependencies and review updates manually |
| Go | Not available | Dependabot/Renovate only |
| Maven/Gradle | Not available | Dependabot/Renovate only |
| NuGet | Not available | Dependabot/Renovate only |
| Composer | Not available | Dependabot/Renovate only |
| RubyGems | Not available | gem.coop proxy / Dependabot/Renovate |

## Conclusion

Expand All @@ -624,6 +671,7 @@ with zero ongoing effort after initial setup. Pick a number, configure it, and s

## Changelog

- **2026-06-03**: Added Bundler (RubyGems) cooldown documentation.
- **2026-06-01**: Added VS Code documentation.
- **2026-05-27**: Added Scala Steward cooldown documentation.
- **2026-05-26**: Added pixi documentation.
Expand Down
68 changes: 63 additions & 5 deletions cooldowns.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@
# cooldowns.sh check
#
# Changelog:
# 2026-06-04 Added bundler support (BUNDLE_COOLDOWN export, Bundler >= 4.0.13)
# 2026-06-01 Added poetry support (solver.min-release-age, poetry >= 2.4.0)
# 2026-05-28 Use pnpm config set --global for pnpm to avoid npm unknown-key warnings
# 2026-05-07 Added pip 26.1+ duration format support (e.g. P3D)
#
# Supported tools: pip, uv, poetry, npm, pnpm, yarn, bun, deno, cargo
# Supported tools: pip, uv, poetry, npm, pnpm, yarn, bun, deno, cargo, bundler
#
# Where configs are written:
# pip Shell wrapper (pip < 26.1) or env var export (pip >= 26.1)
Expand All @@ -32,6 +33,8 @@
# bun minimumReleaseAge in ~/.bunfig.toml
# deno Aliases in /etc/profile.d/cooldowns.sh (or ~/.zshrc / ~/.bashrc)
# cargo COOLDOWN_MINUTES export in /etc/profile.d/cooldowns.sh (or ~/.zshrc / ~/.bashrc)
# bundler BUNDLE_COOLDOWN export in /etc/profile.d/cooldowns.sh (or ~/.zshrc / ~/.bashrc)
# (requires Bundler >= 4.0.13)
#
# Profile scripts fall back to the user's rc file (~/.zshrc or ~/.bashrc, based
# on $SHELL) when /etc/profile.d is not writable.
Expand Down Expand Up @@ -188,6 +191,7 @@ duration_for_tool() {
deno) echo "P${days}D" ;; # ISO 8601
yarn) echo $(( days * 24 * 60 )) ;; # minutes
cargo) echo $(( days * 24 * 60 )) ;; # minutes
bundler) echo "$days" ;; # integer days
*) echo "$days" ;;
esac
}
Expand Down Expand Up @@ -531,10 +535,41 @@ SHELL
echo " Install the crate with: cargo install cargo-cooldown"
}

set_bundler() {
local days="$1"
# Bundler reads BUNDLE_COOLDOWN at runtime; the export is harmless without
# bundler installed, so (like yarn) we don't gate on `command -v bundle`.
local value
value=$(duration_for_tool "$days" bundler)
ensure_profile_dir

if ! find_in_profiles "cooldowns:bundler:start" &>/dev/null; then
if [[ -n "${BUNDLE_COOLDOWN:-}" ]]; then
echo "bundler: BUNDLE_COOLDOWN is already set to '$BUNDLE_COOLDOWN', skipping"
return
fi
local existing_file
if existing_file=$(find_in_profiles "BUNDLE_COOLDOWN="); then
echo "bundler: BUNDLE_COOLDOWN is already configured in $existing_file, skipping"
return
fi
fi

clean_previous bundler "$PROFILE_SCRIPT"

cat >> "$PROFILE_SCRIPT" << SHELL
# cooldowns:bundler:start
export BUNDLE_COOLDOWN="$value"
# cooldowns:bundler:end
SHELL
echo "bundler: set BUNDLE_COOLDOWN=$value in $PROFILE_SCRIPT"
echo " note: requires Bundler >= 4.0.13. Per-project you can instead run 'bundle config set cooldown $value' or add 'cooldown: $value' to a Gemfile source."
}

do_set() {
if [[ $# -lt 2 ]]; then
echo "usage: cooldowns.sh set <tool> <duration>" >&2
echo "tools: pip, uv, poetry, npm, pnpm, yarn, bun, deno, cargo" >&2
echo "tools: pip, uv, poetry, npm, pnpm, yarn, bun, deno, cargo, bundler" >&2
return 1
fi

Expand All @@ -552,9 +587,10 @@ do_set() {
bun) set_bun "$days" ;;
deno) set_deno "$days" ;;
cargo) set_cargo "$days" ;;
bundler) set_bundler "$days" ;;
*)
echo "error: unknown tool '$tool'" >&2
echo "supported: pip, uv, poetry, npm, pnpm, yarn, bun, deno, cargo" >&2
echo "supported: pip, uv, poetry, npm, pnpm, yarn, bun, deno, cargo, bundler" >&2
return 1
;;
esac
Expand Down Expand Up @@ -839,6 +875,24 @@ check_cargo() {
record cargo $STATUS_MISSING "no cooldown configured"
}

check_bundler() {
if [[ -n "${BUNDLE_COOLDOWN:-}" ]]; then
record bundler $STATUS_OK "BUNDLE_COOLDOWN=$BUNDLE_COOLDOWN (${BUNDLE_COOLDOWN}d)"
return
fi

local profile_file
if profile_file=$(find_in_profiles "cooldowns:bundler:start") \
|| profile_file=$(find_in_profiles "BUNDLE_COOLDOWN="); then
local val
val=$(extract_kv BUNDLE_COOLDOWN "$profile_file" || echo "")
record bundler $STATUS_OK "BUNDLE_COOLDOWN=$val (${val}d) in $profile_file (not yet sourced)"
return
fi

record bundler $STATUS_MISSING "no cooldown configured"
}

tool_is_relevant() {
local tool="$1"
command -v "$tool" &>/dev/null && return 0
Expand All @@ -857,6 +911,8 @@ tool_is_relevant() {
find_in_profiles "COOLDOWN_MINUTES=" &>/dev/null && return 0 ;;
yarn) [[ -n "${YARN_NPM_MINIMAL_AGE_GATE:-}" ]] && return 0
find_in_profiles "YARN_NPM_MINIMAL_AGE_GATE=" &>/dev/null && return 0 ;;
bundler) [[ -n "${BUNDLE_COOLDOWN:-}" ]] && return 0
find_in_profiles "BUNDLE_COOLDOWN=" &>/dev/null && return 0 ;;
esac
return 1
}
Expand All @@ -867,7 +923,7 @@ do_check() {

local any_checked=false

for tool in pip uv poetry npm pnpm yarn bun deno cargo; do
for tool in pip uv poetry npm pnpm yarn bun deno cargo bundler; do
if tool_is_relevant "$tool"; then
any_checked=true
"check_${tool}"
Expand Down Expand Up @@ -922,7 +978,7 @@ commands:
set <tool> <duration> Configure cooldown for a package manager
check Check cooldown status for all installed tools

tools: pip, uv, poetry, npm, pnpm, yarn, bun, deno, cargo
tools: pip, uv, poetry, npm, pnpm, yarn, bun, deno, cargo, bundler

duration examples: 3d, "3 days", 7d, 1d

Expand All @@ -938,6 +994,8 @@ where configs are written (all user-wide; project-level configs are not modified
deno shell aliases /etc/profile.d/cooldowns.sh (or ~/.zshrc / ~/.bashrc)
cargo env var export /etc/profile.d/cooldowns.sh (or ~/.zshrc / ~/.bashrc)
(requires cargo-cooldown crate; use 'cargo cooldown <cmd>')
bundler env var export /etc/profile.d/cooldowns.sh (or ~/.zshrc / ~/.bashrc)
(requires Bundler >= 4.0.13)

Fallback chooses ~/.zshrc or ~/.bashrc based on $SHELL.

Expand Down
2 changes: 1 addition & 1 deletion tests/smoke-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ if command -v pnpm &>/dev/null; then
fi

configured_tools=()
for t in pip uv poetry npm pnpm yarn bun deno cargo; do
for t in pip uv poetry npm pnpm yarn bun deno cargo bundler; do
set_out=$(cooldowns.sh set "$t" 7d)
echo "$set_out"
if ! echo "$set_out" | grep -q "skipping" || echo "$set_out" | grep -q "already"; then
Expand Down
Loading