Skip to content

Conversation

@erikaxel
Copy link

Skip cache or bun download if bun with the required version already exists locally. This cuts 5-15 seconds for each install.

Fixes #137

Other tool installers also do this, and without it, bun-setup takes more time than eg. node-setup in our use case. With this fix it was consistently faster.

@coderabbitai
Copy link

coderabbitai bot commented Sep 15, 2025

Walkthrough

Adds a pre-check that detects an existing Bun at bunPath and, if its revision matches the requested version, reuses it (marks cacheHit) and skips cache restoration and download; otherwise preserves existing cache restore and downloads only when no cache hit exists.

Changes

Cohort / File(s) Summary of changes
Action logic: existing Bun detection and cache flow
src/action.ts
- Add pre-check to read existing revision from bunPath and handle errors.
- Add isVersionMatch(existingRevision, requestedVersion) to compare version portions (ignore +hash), treating empty/non-pinned requests as non-matching.
- If match, mark cacheHit, log reuse, and skip cache restore and download; otherwise retain prior cache restore logic and download only when no cache hit.
- No public API changes.

Pre-merge checks

✅ Passed checks (4 passed)
Check name Status Explanation
Title Check ✅ Passed The title “feat: Check for existing bun before downloading” concisely and accurately summarizes the primary change of detecting and reusing an existing Bun installation to skip unnecessary downloads. It clearly conveys the feature intent without extraneous details and aligns with the code adjustments described in the changeset.
Linked Issues Check ✅ Passed The implementation adds logic to detect an existing Bun binary, uses isVersionMatch to compare versions, skips cache restoration and network download when versions match, and supports self-hosted runner reuse as specified in issue #137. These changes fulfill the core coding objectives of version detection, download avoidance, and alignment with prior tool setups.
Out of Scope Changes Check ✅ Passed All modifications pertain exclusively to adding pre-download checks, version comparison, and logging for existing Bun installations, with no unrelated or extraneous changes outside the scope of issue #137’s requirements.
Description Check ✅ Passed The pull request description clearly explains that the change skips cache and download steps when a locally installed Bun matches the required version, highlights the performance benefit, and references the related issue #137. It directly relates to the code modifications and provides sufficient context for reviewers without being off-topic.

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

@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.

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/action.ts (2)

94-97: Avoid logging undefined revision on “corrupted” cache.

Revision is undefined here; message is confusing. Log path/key instead.

Apply this diff:

-          warning(
-            `Found a cached version of Bun: ${revision} (but it appears to be corrupted?)`,
-          );
+          warning("Cache restored but Bun version could not be read; cache may be corrupted.");

110-112: Fix inaccurate error message.

This can trigger even when reusing existing or cached binaries.

Apply this diff:

-      "Downloaded a new version of Bun, but failed to check its version? Try again.",
+      "Failed to determine Bun revision after setup. Try again.",
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 22457c8 and d9e7941.

⛔ Files ignored due to path filters (1)
  • dist/setup/index.js is excluded by !**/dist/**
📒 Files selected for processing (1)
  • src/action.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/action.ts (1)
src/utils.ts (1)
  • retry (6-19)
🪛 Biome (2.1.2)
src/action.ts

[error] 145-145: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)

🔇 Additional comments (1)
src/action.ts (1)

101-105: Fallback download path with retry looks good.

Copy link

@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.

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/action.ts (2)

135-146: Syntax error and missing robustness checks.

The issues flagged in the previous review are still present:

  1. Syntax error on line 145: Extra ) causing parse failure
  2. Missing non-pinned value check: Should return false for latest, canary, action (these mean "fetch latest", not "match existing")
  3. Missing version normalization: Should strip leading v from both sides before comparison

Apply the diff from the previous review:

-function isVersionMatch(existingRevision: string, requestedVersion?: string): boolean {
-  // If no version specified, default is "latest" - don't match existing
-  if (!requestedVersion) {
-    return false;
-  }
-
-  // Extract version from revision (format: "1.2.3+hash" or "1.2.3")
-  const [existingVersion] = existingRevision.split("+");
-  
-  // We only return true if version matches exactly, 'latest' will never match
-  return existingVersion === requestedVersion)
-}
+function isVersionMatch(
+  existingRevision: string,
+  requestedVersion?: string,
+): boolean {
+  if (!requestedVersion) return false;
+  // Ignore non-pinned values
+  if (/^(latest|canary|action)$/i.test(requestedVersion)) return false;
+  // Normalize 'v' prefix and compare exact semver
+  const [existingVersion] = existingRevision.trim().split("+");
+  const normalizedReq = requestedVersion.trim().replace(/^v/i, "");
+  return existingVersion.replace(/^v/i, "") === normalizedReq;
+}

78-83: Critical: Skip existing installation check when customUrl is provided.

When options.customUrl is set, the user intends to use a specific custom build. Reusing an existing local binary (even if the version matches) could result in using the wrong build variant, hash, or configuration.

Apply this diff to skip reuse when customUrl is provided:

   // Check if Bun executable already exists and matches requested version
   const existingRevision = await getRevision(bunPath);
-  if (existingRevision && isVersionMatch(existingRevision, options.version)) {
+  if (!options.customUrl && existingRevision && isVersionMatch(existingRevision, options.version)) {
     revision = existingRevision;
     cacheHit = true; // Treat as cache hit to avoid unnecessary network requests
     info(`Using existing Bun installation: ${revision}`);

Note: The try-catch in getRevision (lines 219-238) now handles ENOENT gracefully by returning undefined, so the explicit existsSync guard suggested in previous reviews is no longer strictly necessary. However, adding existsSync would make the intent clearer and avoid an unnecessary async call when the file doesn't exist.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f8de834 and 9608f34.

⛔ Files ignored due to path filters (1)
  • dist/setup/index.js is excluded by !**/dist/**
📒 Files selected for processing (1)
  • src/action.ts (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/action.ts (1)
src/utils.ts (1)
  • retry (6-19)
🔇 Additional comments (1)
src/action.ts (1)

219-238: LGTM: Robust error handling.

The try-catch wrapper correctly handles cases where the executable doesn't exist or fails to run, returning undefined instead of throwing. This graceful degradation is essential for the early-exit optimization in lines 78-83.

src/action.ts Outdated
Comment on lines 80 to 82
revision = existingRevision;
cacheHit = true; // Treat as cache hit to avoid unnecessary network requests
info(`Using existing Bun installation: ${revision}`);
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider clarifying the semantic difference between cache hit and existing installation.

Setting cacheHit = true when reusing an existing installation (line 81) conflates two distinct concepts:

  • Cache hit: Restored from actions/cache
  • Existing installation: Found locally without cache lookup

While both avoid downloads (same practical effect), downstream consumers of cacheHit might assume it indicates actions/cache was used. Consider introducing a separate flag like existingInstallation or adjusting the output structure to distinguish these cases.

🤖 Prompt for AI Agents
In src/action.ts around lines 80-82, setting cacheHit = true when reusing an
existing local installation conflates “restored from actions/cache” with “found
locally”; introduce a new boolean (e.g., existingInstallation) or update the
output structure to distinguish the two, set cacheHit only when restored from
cache and set existingInstallation = true when the install was reused from the
local system, and update any downstream references/logging/outputs that read
cacheHit to also consider the new flag.

Check for existing bun with same version number, if it exists we skip any futher actions. This avoids downloading cache or bun over the network.
Copy link

@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.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8876d4f and 89d06fc.

📒 Files selected for processing (1)
  • src/action.ts (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/action.ts (1)
src/utils.ts (1)
  • retry (6-19)
🔇 Additional comments (2)
src/action.ts (2)

10-10: LGTM!

The existsSync import is correctly added and properly used to guard the getRevision call.


88-111: LGTM!

The restructured logic correctly prioritizes existing installations over cache restoration and downloads. The flow is clear and maintains backward compatibility.

Comment on lines +78 to 86
// Check if Bun executable already exists and matches requested version
if (!options.customUrl && existsSync(bunPath)) {
const existingRevision = await getRevision(bunPath);
if (existingRevision && isVersionMatch(existingRevision, options.version)) {
revision = existingRevision;
cacheHit = true; // Treat as cache hit to avoid unnecessary network requests
info(`Using existing Bun installation: ${revision}`);
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add error handling to gracefully fall back when getRevision fails.

If getRevision throws an error (e.g., file exists but is corrupted or not executable), the action will fail instead of falling back to download or cache restoration. This breaks the robustness of the setup action.

Apply this diff to add error handling:

   // Check if Bun executable already exists and matches requested version
   if (!options.customUrl && existsSync(bunPath)) {
-    const existingRevision = await getRevision(bunPath);
-    if (existingRevision && isVersionMatch(existingRevision, options.version)) {
-      revision = existingRevision;
-      cacheHit = true; // Treat as cache hit to avoid unnecessary network requests
-      info(`Using existing Bun installation: ${revision}`);
+    try {
+      const existingRevision = await getRevision(bunPath);
+      if (existingRevision && isVersionMatch(existingRevision, options.version)) {
+        revision = existingRevision;
+        cacheHit = true; // Treat as cache hit to avoid unnecessary network requests
+        info(`Using existing Bun installation: ${revision}`);
+      }
+    } catch (error) {
+      // Existing installation is invalid, fall back to cache/download
+      info(`Existing Bun installation is invalid: ${error.message}`);
     }
   }

Note: The semantic concern about conflating cacheHit with existing installation (flagged in previous reviews) remains unaddressed but is a known nitpick.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Check if Bun executable already exists and matches requested version
if (!options.customUrl && existsSync(bunPath)) {
const existingRevision = await getRevision(bunPath);
if (existingRevision && isVersionMatch(existingRevision, options.version)) {
revision = existingRevision;
cacheHit = true; // Treat as cache hit to avoid unnecessary network requests
info(`Using existing Bun installation: ${revision}`);
}
}
// Check if Bun executable already exists and matches requested version
if (!options.customUrl && existsSync(bunPath)) {
try {
const existingRevision = await getRevision(bunPath);
if (existingRevision && isVersionMatch(existingRevision, options.version)) {
revision = existingRevision;
cacheHit = true; // Treat as cache hit to avoid unnecessary network requests
info(`Using existing Bun installation: ${revision}`);
}
} catch (error) {
// Existing installation is invalid, fall back to cache/download
info(`Existing Bun installation is invalid: ${error.message}`);
}
}
🤖 Prompt for AI Agents
In src/action.ts around lines 78 to 86, calling getRevision(bunPath) can throw
(e.g., corrupted or non-executable file) and currently will crash the action;
wrap the getRevision call in a try/catch, and on error log a warning including
the error and the path, ensure you do not set revision or cacheHit in that
failure case so the code falls through to the normal download/cache-restore
path, and proceed without rethrowing the error.

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.

Using already installed Bun if available

1 participant