Skip to content

Enforce minimum Bun version 1.3.6 at runtime#334

Merged
subsy merged 5 commits intomainfrom
claude/check-bun-version-requirement-pH4Cy
Feb 23, 2026
Merged

Enforce minimum Bun version 1.3.6 at runtime#334
subsy merged 5 commits intomainfrom
claude/check-bun-version-requirement-pH4Cy

Conversation

@subsy
Copy link
Copy Markdown
Owner

@subsy subsy commented Feb 23, 2026

Summary

This PR adds runtime validation to ensure ralph-tui is executed with Bun version 1.3.6 or higher. The application will now fail fast with a clear error message if run on an older Bun version, preventing cryptic runtime errors.

Key Changes

  • Added checkBunVersion() function in src/utils/validation.ts that compares the current Bun version against a minimum required version using semantic versioning comparison
  • Integrated version check into CLI entry point (src/cli.tsx) to validate Bun version before any other initialization, with graceful error messaging and exit
  • Updated package.json to reflect the new minimum Bun version requirement (1.3.6) in the engines field
  • Added comprehensive test suite (src/utils/validation.test.ts) with 10 test cases covering:
    • Exact version matches
    • Higher patch/minor/major versions (all pass)
    • Lower patch/minor/major versions (all fail with appropriate error messages)
    • Pre-release and build metadata handling
    • Numeric comparison edge cases (e.g., 1.10.0 > 1.9.0)

Implementation Details

  • The version check leverages the existing compareSemverStrings() utility from the migration module for consistent semantic version comparison
  • Error messages include the minimum required version, current version, and a link to Bun installation docs
  • The check runs early in the CLI initialization to fail fast before any complex operations
  • Pre-release versions (e.g., 1.3.6-beta) and build metadata (e.g., 1.3.6+build123) are properly handled by stripping them before comparison

https://claude.ai/code/session_01RuyCDncNpp7xJoDRLoq8Vt

Summary by CodeRabbit

  • New Features

    • Application now validates Bun version at startup and shows clear upgrade guidance if required.
  • Chores

    • Minimum required Bun version updated to 1.3.6.
  • Tests

    • Added comprehensive tests for version validation and semver comparisons, including pre-release and build metadata.
  • Refactor

    • Semver comparison logic consolidated into a shared utility for consistent version handling.

Update engines.bun in package.json from >=1.0.0 to >=1.3.6 and add a
runtime version check at CLI startup that exits with a clear error
message if the user's Bun version is too old.

https://claude.ai/code/session_01RuyCDncNpp7xJoDRLoq8Vt
Move the version comparison logic from cli.tsx into a testable
checkBunVersion function in src/utils/validation.ts. Add 10 test
cases covering exact match, higher/lower major/minor/patch versions,
pre-release metadata stripping, and numeric segment ordering.

https://claude.ai/code/session_01RuyCDncNpp7xJoDRLoq8Vt
@vercel
Copy link
Copy Markdown

vercel bot commented Feb 23, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
ralph-tui Ignored Ignored Preview Feb 23, 2026 5:17pm

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 23, 2026

Walkthrough

Adds Bun runtime version validation: package.json minimum Bun bumped to 1.3.6; new semver comparator and validation utilities; CLI checks Bun.version at startup and exits if running Bun is older; tests added and migration refactor uses shared semver util.

Changes

Cohort / File(s) Summary
Configuration
package.json
Bumped engines.bun from >=1.0.0 to >=1.3.6 to match packageManager: "bun@1.3.6".
CLI integration
src/cli.tsx
Imports pkg and checkBunVersion, derives minimum Bun from pkg.engines.bun, validates Bun.version at startup and exits with an error message when below minimum; version output now uses pkg.version.
Semver util
src/utils/semver.ts, src/utils/semver.test.ts
Added compareSemverStrings() to compare semver-like strings (strips leading v, ignores pre-release/build metadata, numeric segment comparison) and tests covering equality, ordering, missing segments and metadata.
Validation util & tests
src/utils/validation.ts, src/utils/validation.test.ts
Added checkBunVersion(current, min) using compareSemverStrings(); returns formatted error when current < min. Tests cover matching, higher/lower versions, pre-release/build metadata handling and message contents.
Migration refactor & tests
src/setup/migration.ts, src/setup/migration.test.ts
Removed local compareSemverStrings implementation and its tests; migration.ts now imports compareSemverStrings from ../utils/semver.js.

Sequence Diagram(s)

sequenceDiagram
  participant CLI as CLI (startup)
  participant Pkg as package.json
  participant Val as Validation util
  participant Bun as Bun runtime

  CLI->>Pkg: read engines.bun (minVersion)
  CLI->>Bun: check if Bun exists / get Bun.version
  alt Bun present
    CLI->>Val: checkBunVersion(Bun.version, minVersion)
    Val-->>CLI: null (ok) / errorMessage
    alt errorMessage
      CLI->>Bun: exit with errorMessage
    else ok
      CLI->>CLI: continue startup (show help / run commands)
    end
  else Bun absent
    CLI->>CLI: skip Bun validation, continue startup
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title directly and accurately describes the main change: enforcing a minimum Bun version requirement at runtime. It is concise, specific, and clearly summarises the primary objective.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch claude/check-bun-version-requirement-pH4Cy

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
src/utils/validation.ts (2)

6-6: Optional: consider co-locating compareSemverStrings with the validation module.

validation.ts is a general-purpose utility, but it now carries a dependency on setup/migration.ts. If migration.ts is ever split or its exports change, this silent coupling breaks version checks. Moving compareSemverStrings into validation.ts (or a dedicated src/utils/semver.ts) would eliminate the dependency and make migration.ts the consumer rather than the source.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/validation.ts` at line 6, validation.ts currently imports
compareSemverStrings from setup/migration.js creating an unnecessary coupling;
extract the compareSemverStrings implementation into validation.ts (or a new
src/utils/semver.ts) and update all imports (e.g., in migration.ts and any other
modules) to consume it from the new location so migration.ts becomes a consumer,
not the source; ensure exported name stays compareSemverStrings and run/adjust
any unit tests or type exports that referenced the old path.

261-264: Consider including bun upgrade in the error message for immediate actionability.

The current URL (https://bun.sh/docs/installation) is an install guide. Users who already have an older Bun installed are better served by the upgrade command directly. bun upgrade is the built-in command for upgrading Bun, and a dedicated upgrade guide is available at https://bun.com/docs/guides/util/upgrade.

💡 Suggested wording
-      `Please upgrade: https://bun.sh/docs/installation`
+      `Please upgrade by running: bun upgrade\n` +
+      `See also: https://bun.com/docs/guides/util/upgrade`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/validation.ts` around lines 261 - 264, The version-mismatch message
that uses minVersion and currentVersion should be updated to include an
actionable upgrade hint; in the return statement that builds the string using
minVersion and currentVersion (where the code currently returns `ralph-tui
requires Bun >= ${minVersion}... Bun ${currentVersion}`), append a suggestion to
run `bun upgrade` and include the upgrade guide URL
(`https://bun.com/docs/guides/util/upgrade`) alongside or instead of the
installation docs so users have an immediate remediation path.
src/utils/validation.test.ts (1)

49-61: Consider adding a test for a pre-release version that falls below the minimum.

The suite covers 1.3.6-beta (pre-release of the minimum itself) and build metadata, but not 1.3.5-beta — a pre-release of a version that is lower than the minimum. The implementation handles it correctly (metadata stripped → 1.3.5 < 1.3.6 → error), but an explicit test would document the intended behaviour.

+  test('returns error for pre-release version below minimum', () => {
+    const result = checkBunVersion('1.3.5-beta', MIN_VERSION);
+    expect(result).not.toBeNull();
+    expect(result).toContain('requires Bun >= 1.3.6');
+  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/validation.test.ts` around lines 49 - 61, Add a test in
src/utils/validation.test.ts that verifies pre-release versions below the
minimum produce an error: call checkBunVersion('1.3.5-beta', MIN_VERSION) and
assert it is NOT null (or matches the existing error shape). Place it alongside
the other tests (e.g., name it "pre-release below minimum returns error") so it
documents that '1.3.5-beta' is treated as 1.3.5 and thus fails against
MIN_VERSION; reference the checkBunVersion function and MIN_VERSION constant
used in the file.
src/cli.tsx (1)

28-29: MIN_BUN_VERSION is a second source of truth alongside package.json.

engines.bun in package.json already declares >=1.3.6. If the minimum version is bumped in the future, both this constant and the manifest need updating in sync. A brief comment cross-referencing package.json would reduce the risk of the two drifting apart.

💡 Suggested addition
-/** Minimum bun version required to run ralph-tui. */
+/** Minimum bun version required to run ralph-tui. Keep in sync with engines.bun in package.json. */
 const MIN_BUN_VERSION = '1.3.6';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli.tsx` around lines 28 - 29, The MIN_BUN_VERSION constant is a second
source of truth; update the comment above MIN_BUN_VERSION to explicitly
reference the engines.bun entry in package.json (e.g., "See package.json
engines.bun for the authoritative minimum") and add a short note to keep both
values in sync when bumping versions — alternatively, replace the hardcoded
MIN_BUN_VERSION with code that reads engines.bun from package.json at runtime if
you prefer a single source of truth.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/cli.tsx`:
- Around line 28-29: The MIN_BUN_VERSION constant is a second source of truth;
update the comment above MIN_BUN_VERSION to explicitly reference the engines.bun
entry in package.json (e.g., "See package.json engines.bun for the authoritative
minimum") and add a short note to keep both values in sync when bumping versions
— alternatively, replace the hardcoded MIN_BUN_VERSION with code that reads
engines.bun from package.json at runtime if you prefer a single source of truth.

In `@src/utils/validation.test.ts`:
- Around line 49-61: Add a test in src/utils/validation.test.ts that verifies
pre-release versions below the minimum produce an error: call
checkBunVersion('1.3.5-beta', MIN_VERSION) and assert it is NOT null (or matches
the existing error shape). Place it alongside the other tests (e.g., name it
"pre-release below minimum returns error") so it documents that '1.3.5-beta' is
treated as 1.3.5 and thus fails against MIN_VERSION; reference the
checkBunVersion function and MIN_VERSION constant used in the file.

In `@src/utils/validation.ts`:
- Line 6: validation.ts currently imports compareSemverStrings from
setup/migration.js creating an unnecessary coupling; extract the
compareSemverStrings implementation into validation.ts (or a new
src/utils/semver.ts) and update all imports (e.g., in migration.ts and any other
modules) to consume it from the new location so migration.ts becomes a consumer,
not the source; ensure exported name stays compareSemverStrings and run/adjust
any unit tests or type exports that referenced the old path.
- Around line 261-264: The version-mismatch message that uses minVersion and
currentVersion should be updated to include an actionable upgrade hint; in the
return statement that builds the string using minVersion and currentVersion
(where the code currently returns `ralph-tui requires Bun >= ${minVersion}...
Bun ${currentVersion}`), append a suggestion to run `bun upgrade` and include
the upgrade guide URL (`https://bun.com/docs/guides/util/upgrade`) alongside or
instead of the installation docs so users have an immediate remediation path.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f7cd7fd and 3cc8f66.

📒 Files selected for processing (4)
  • package.json
  • src/cli.tsx
  • src/utils/validation.test.ts
  • src/utils/validation.ts

- Extract compareSemverStrings into src/utils/semver.ts so migration.ts
  is a consumer, not the source; move its tests to semver.test.ts
- Read MIN_BUN_VERSION from package.json engines.bun at runtime instead
  of hardcoding, making package.json the single source of truth
- Update error message with 'bun upgrade' hint for immediate remediation
- Add test for pre-release version below minimum (1.3.5-beta < 1.3.6)

https://claude.ai/code/session_01RuyCDncNpp7xJoDRLoq8Vt
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/cli.tsx (1)

159-160: The dynamic package.json import inside handleSubcommand is now redundant and shadows the module-level pkg.

The static import added at line 27 (import pkg from '../package.json') already provides the JSON object as pkg. The local const pkg = await import(…) re-imports the same file, shadows the outer pkg with a module-namespace object (requiring .default.version instead of .version), and adds an unnecessary async dependency to the version branch.

♻️ Suggested simplification
-    // Dynamic import to get version from package.json
-    const pkg = await import('../package.json', { with: { type: 'json' } });
-    console.log(`ralph-tui ${pkg.default.version}`);
+    console.log(`ralph-tui ${pkg.version}`);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli.tsx` around lines 159 - 160, In handleSubcommand, remove the local
dynamic import that shadows the module-level pkg; eliminate "const pkg = await
import(...)" and use the already-imported top-level pkg (import pkg from
'../package.json') so console.log uses pkg.version instead of
pkg.default.version, avoiding the unnecessary await/async dependency and
shadowing; update the console.log call in handleSubcommand accordingly.
src/utils/semver.ts (1)

15-21: v-prefixed strings silently compare as 0.x.y.

parseInt("v1", 10) returns NaN, which || 0 converts to 0, so "v1.3.6" is treated as "0.3.6". Current callers (Bun.version, config version strings) never produce a v prefix, so this is benign today, but as the utility gains callers it could bite. A one-liner guard would future-proof it:

♻️ Optional robustness fix
 export function compareSemverStrings(a: string, b: string): -1 | 0 | 1 {
-  // Strip any pre-release/build metadata (e.g., "2.0-beta" -> "2.0")
-  const cleanA = a.split(/[-+]/)[0];
-  const cleanB = b.split(/[-+]/)[0];
+  // Strip leading 'v' and any pre-release/build metadata (e.g., "v2.0-beta" -> "2.0")
+  const cleanA = a.replace(/^v/i, '').split(/[-+]/)[0];
+  const cleanB = b.replace(/^v/i, '').split(/[-+]/)[0];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/semver.ts` around lines 15 - 21, compareSemverStrings silently
treats "v"-prefixed versions as 0.x.y because parseInt returns NaN; fix by
normalizing inputs to strip a leading "v" (case-insensitive) before removing
pre-release/build metadata so "v1.3.6" becomes "1.3.6"; update the cleanA/cleanB
logic in compareSemverStrings (e.g., apply .replace(/^v/i, '') before
.split(/[-+]/)[0]) so parsing to integers works correctly for v-prefixed
strings.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/cli.tsx`:
- Around line 159-160: In handleSubcommand, remove the local dynamic import that
shadows the module-level pkg; eliminate "const pkg = await import(...)" and use
the already-imported top-level pkg (import pkg from '../package.json') so
console.log uses pkg.version instead of pkg.default.version, avoiding the
unnecessary await/async dependency and shadowing; update the console.log call in
handleSubcommand accordingly.

In `@src/utils/semver.ts`:
- Around line 15-21: compareSemverStrings silently treats "v"-prefixed versions
as 0.x.y because parseInt returns NaN; fix by normalizing inputs to strip a
leading "v" (case-insensitive) before removing pre-release/build metadata so
"v1.3.6" becomes "1.3.6"; update the cleanA/cleanB logic in compareSemverStrings
(e.g., apply .replace(/^v/i, '') before .split(/[-+]/)[0]) so parsing to
integers works correctly for v-prefixed strings.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3cc8f66 and b879814.

📒 Files selected for processing (7)
  • src/cli.tsx
  • src/setup/migration.test.ts
  • src/setup/migration.ts
  • src/utils/semver.test.ts
  • src/utils/semver.ts
  • src/utils/validation.test.ts
  • src/utils/validation.ts
💤 Files with no reviewable changes (1)
  • src/setup/migration.test.ts

- Use top-level pkg import for version command instead of redundant
  dynamic import that shadowed the module-level binding
- Strip leading "v" prefix in compareSemverStrings so "v1.3.6" parses
  correctly instead of silently becoming 0.3.6 via parseInt("v1") → NaN

https://claude.ai/code/session_01RuyCDncNpp7xJoDRLoq8Vt
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/utils/semver.test.ts (1)

39-43: Consider documenting the intentional SemVer spec deviation in the pre-release test.

Per the SemVer spec, a pre-release tag makes a version lower than the corresponding release (2.0-alpha < 2.0), so compareSemverStrings('2.0-alpha', '2.0') returning 0 rather than -1 is a deliberate non-spec behaviour. The test name describes the mechanism ("strips … metadata") but not the semantic consequence, which can mislead future maintainers into assuming full spec compliance.

A short comment and/or an extra assertion makes the intent explicit:

✏️ Suggested annotation
  test('strips pre-release and build metadata', () => {
+    // NOTE: Intentionally deviates from SemVer §9 — pre-release tags are
+    // stripped rather than being given lower precedence than the release.
+    // This means '2.0-alpha' is treated as equal to '2.0', not less-than.
    expect(compareSemverStrings('2.0-beta', '2.0')).toBe(0);
    expect(compareSemverStrings('2.0+build123', '2.0')).toBe(0);
    expect(compareSemverStrings('2.0-alpha', '2.0-beta')).toBe(0);
+    // Consequence for the Bun version check: '1.3.6-rc1' is treated as '1.3.6' and passes.
+    expect(compareSemverStrings('1.3.6-rc1', '1.3.6')).toBe(0);
  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/semver.test.ts` around lines 39 - 43, The test in semver.test.ts
uses compareSemverStrings and currently asserts that pre-release/build metadata
are stripped (e.g., compareSemverStrings('2.0-alpha','2.0') === 0) but doesn't
document that this deliberately deviates from SemVer (where '2.0-alpha' <
'2.0'); update the test by adding a short inline comment next to the
compareSemverStrings calls explaining the intentional non‑spec behavior, and
optionally add an extra assertion or note that the SemVer spec would expect -1
for '2.0-alpha' vs '2.0' to make the semantic consequence explicit for future
maintainers while keeping the existing assertions intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/utils/semver.test.ts`:
- Around line 39-43: The test in semver.test.ts uses compareSemverStrings and
currently asserts that pre-release/build metadata are stripped (e.g.,
compareSemverStrings('2.0-alpha','2.0') === 0) but doesn't document that this
deliberately deviates from SemVer (where '2.0-alpha' < '2.0'); update the test
by adding a short inline comment next to the compareSemverStrings calls
explaining the intentional non‑spec behavior, and optionally add an extra
assertion or note that the SemVer spec would expect -1 for '2.0-alpha' vs '2.0'
to make the semantic consequence explicit for future maintainers while keeping
the existing assertions intact.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b879814 and 8a9ae9f.

📒 Files selected for processing (3)
  • src/cli.tsx
  • src/utils/semver.test.ts
  • src/utils/semver.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/utils/semver.ts
  • src/cli.tsx

@subsy subsy merged commit fabf2db into main Feb 23, 2026
7 checks passed
@subsy subsy deleted the claude/check-bun-version-requirement-pH4Cy branch February 23, 2026 17:24
@codecov
Copy link
Copy Markdown

codecov bot commented Feb 23, 2026

Codecov Report

❌ Patch coverage is 51.72414% with 14 lines in your changes missing coverage. Please review.
✅ Project coverage is 45.96%. Comparing base (f7cd7fd) to head (8a9ae9f).
⚠️ Report is 6 commits behind head on main.

Files with missing lines Patch % Lines
src/utils/validation.ts 11.11% 8 Missing ⚠️
src/utils/semver.ts 68.42% 6 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #334      +/-   ##
==========================================
- Coverage   45.97%   45.96%   -0.01%     
==========================================
  Files         100      101       +1     
  Lines       32263    32273      +10     
==========================================
+ Hits        14833    14835       +2     
- Misses      17430    17438       +8     
Files with missing lines Coverage Δ
src/setup/migration.ts 63.46% <100.00%> (-0.26%) ⬇️
src/utils/semver.ts 68.42% <68.42%> (ø)
src/utils/validation.ts 82.89% <11.11%> (-4.52%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants