Skip to content

chore: migrate from Node.js/npm to Bun#612

Merged
yannickmonney merged 9 commits into
mainfrom
chore/migrate-to-bun
Mar 1, 2026
Merged

chore: migrate from Node.js/npm to Bun#612
yannickmonney merged 9 commits into
mainfrom
chore/migrate-to-bun

Conversation

@yannickmonney
Copy link
Copy Markdown
Contributor

@yannickmonney yannickmonney commented Mar 1, 2026

Replace Node.js/npm with Bun across the entire monorepo:

  • Set packageManager to bun@1.3.10 in root package.json
  • Replace all npm/npx commands with bun/bunx in scripts, CI, Docker
  • Replace Express.js production server with native Bun.serve()
  • Remove express and @types/express dependencies
  • Delete package-lock.json, add bun.lock
  • Delete .nvmrc files (no longer needed)
  • Update GitHub Actions to use oven-sh/setup-bun@v2
  • Update Dockerfile to use oven/bun:latest base image
  • Update all shell scripts shebangs and commands
  • Add package-lock.json to .gitignore

Summary by CodeRabbit

  • Chores

    • Migrated development toolchain from Node.js/npm to Bun as the primary JavaScript runtime and package manager.
    • Updated setup instructions, development commands, and CI/CD workflows to use Bun throughout.
    • Updated Docker configuration and deployment scripts to use Bun.
  • Refactor

    • Replaced Express-based server implementation with Bun's native HTTP server.

Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 1, 2026

📝 Walkthrough

Walkthrough

This pull request migrates the project from npm and Node.js to Bun as the primary package manager and runtime. The migration includes: removing Node.js version pinning (.nvmrc files), updating the packageManager field in package.json to Bun, replacing all npm/npx commands with bun/bunx equivalents across scripts and workflows, updating GitHub Actions workflows to use Bun setup instead of Node setup, modifying the Dockerfile to include Bun binary stages, rewriting services/platform/server.js from Express to Bun's native HTTP server, and updating all documentation and configuration files to reflect Bun-based commands.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • Israeltheminer
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'chore: migrate from Node.js/npm to Bun' accurately and concisely summarizes the primary change across the entire changeset—migrating from Node.js/npm tooling to Bun.

✏️ 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 chore/migrate-to-bun

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

@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: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/actions/setup-turbo/action.yml:
- Around line 5-7: The action's input default uses an unstable 'latest' for
bun-version; change the default value for the bun-version input (symbol:
bun-version) from 'latest' to a pinned version (e.g., '1.3.10') so CI is
reproducible across runs—update the default in the action.yml input definition
accordingly to match the packageManager setting used elsewhere.

In @.github/workflows/lint.yml:
- Around line 126-129: The GitHub Actions step "Setup Bun" currently sets
bun-version: latest which risks non-reproducible CI; change the step to pin a
specific Bun version (e.g., bun@1.3.10) by setting bun-version to that exact
version string, or implement logic to read the Bun version from package.json's
packageManager field and pass it to the "Setup Bun" step so the CI uses the
project-specified version instead of latest.

In `@services/platform/Dockerfile`:
- Around line 7-8: The Dockerfile uses an unpinned base image "FROM
oven/bun:latest AS bun-bin" which can lead to non-reproducible builds; change
the FROM line to pin the Bun image to the project packageManager version (e.g.,
use "oven/bun:1.3.10") so the bun-bin stage consistently uses the same Bun
release as declared in packageManager.

In `@services/platform/scripts/generate-admin-password.mjs`:
- Line 1: No code change is required—the bcryptjs usage and the new shebang in
generate-admin-password.mjs are valid under Bun; either leave the script as-is
or, if you want to remove the bcryptjs dependency, replace the bcryptjs import
and hash/compare calls with Bun's built-in API by deleting the bcryptjs
require/import and using Bun.password(...) to create the hash (and the
corresponding Bun verification method) in the functions that currently call
bcryptjs hashing/comparison.

In `@services/platform/server.js`:
- Around line 41-43: The code reads indexHtmlTemplate from
Bun.file(join(distDir, 'index.html')).text() without handling a missing file;
wrap that read in a try/catch (or check existence via Bun.file(...).exists())
around the indexHtmlTemplate assignment in the server startup logic so a missing
dist/index.html doesn't throw; on failure log a clear error including distDir
and the filename, set indexHtmlTemplate to a small safe fallback HTML (or null)
and ensure request handlers return a 500 or the fallback page if
indexHtmlTemplate is absent to avoid unhandled exceptions (refer to
indexHtmlTemplate, Bun.file, join, distDir).
- Around line 31-39: The current check using resolve(pathname.slice(1)) and
startsWith(distDir) can be bypassed by symlinks; update the logic around
filePath/Bun.file to also resolve real paths for both the candidate file and
distDir (e.g., use a realpath call) and compare those canonical paths to ensure
the file actually resides inside distDir, then verify existence via
Bun.file(...).exists() before returning the Response; reference the variables
pathname, distDir, filePath and the Bun.file existence check when making this
change.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7fb13eb and 0a5ac32.

⛔ Files ignored due to path filters (6)
  • bun.lock is excluded by !**/*.lock
  • package-lock.json is excluded by !**/package-lock.json
  • services/platform/convex/migrations/backfill_thread_metadata.ts is excluded by !**/migrations/**
  • services/platform/convex/migrations/backfill_workflow_schedules.ts is excluded by !**/migrations/**
  • services/platform/convex/migrations/remove_deprecated_llm_fields.ts is excluded by !**/migrations/**
  • services/platform/convex/migrations/trigger_steps_to_start.ts is excluded by !**/migrations/**
📒 Files selected for processing (44)
  • .agents/react-doctor/AGENTS.md
  • .agents/react-doctor/SKILL.md
  • .claude/CLAUDE.md
  • .claude/hooks/format.sh
  • .github/actions/setup-turbo/action.yml
  • .github/workflows/build.yml
  • .github/workflows/commitlint.yml
  • .github/workflows/lint.yml
  • .github/workflows/test.yml
  • .gitignore
  • .husky/commit-msg
  • .nvmrc
  • README.md
  • docs/zero-downtime-deployment.md
  • knip.config.ts
  • package.json
  • patches/convex-helpers@0.1.113.patch
  • services/platform/.nvmrc
  • services/platform/Dockerfile
  • services/platform/convex/README.md
  • services/platform/convex/betterAuth/generated_schema.ts
  • services/platform/convex/betterAuth/schema.ts
  • services/platform/convex/integrations/create_integration.ts
  • services/platform/convex/workflow_engine/types/nodes.ts
  • services/platform/docker-entrypoint.sh
  • services/platform/docs/sql-integrations/README.md
  • services/platform/package.json
  • services/platform/scripts/README.md
  • services/platform/scripts/dev.mjs
  • services/platform/scripts/encrypt-inline-secret.mjs
  • services/platform/scripts/generate-admin-password.mjs
  • services/platform/scripts/generate-openapi.ts
  • services/platform/scripts/sync-convex-env-from-dotenv.mjs
  • services/platform/server.js
  • services/platform/stress-tests/poll.ts
  • services/platform/stress-tests/run-stress-test.ts
  • services/platform/stress-tests/scenarios/concurrent-starts.ts
  • services/platform/stress-tests/scenarios/loop-contention.ts
  • services/platform/stress-tests/scenarios/payload-pressure.ts
  • services/platform/stress-tests/scenarios/scheduler-overlap.ts
  • services/platform/stress-tests/scenarios/shard-comparison.ts
  • services/platform/stress-tests/scenarios/sustained-load.ts
  • tests/PROTEL_INTEGRATION_TESTING.md
  • tools/cli/package.json
💤 Files with no reviewable changes (2)
  • services/platform/.nvmrc
  • .nvmrc

Comment thread .github/actions/setup-turbo/action.yml Outdated
Comment on lines +5 to +7
bun-version:
description: Bun version
default: 'latest'
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider pinning the default Bun version for reproducibility.

The default bun-version: 'latest' could lead to CI inconsistencies when new Bun versions are released. Since this composite action is used across multiple workflows, pinning the version here (e.g., to 1.3.10 per the PR's packageManager setting) would ensure consistent behavior across all jobs.

♻️ Proposed fix to pin default version
 inputs:
   bun-version:
     description: Bun version
-    default: 'latest'
+    default: '1.3.10'
📝 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
bun-version:
description: Bun version
default: 'latest'
bun-version:
description: Bun version
default: '1.3.10'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/actions/setup-turbo/action.yml around lines 5 - 7, The action's
input default uses an unstable 'latest' for bun-version; change the default
value for the bun-version input (symbol: bun-version) from 'latest' to a pinned
version (e.g., '1.3.10') so CI is reproducible across runs—update the default in
the action.yml input definition accordingly to match the packageManager setting
used elsewhere.

Comment thread .github/workflows/lint.yml Outdated
Comment on lines +126 to +129
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
node-version: '24'
cache: 'npm'
bun-version: latest
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider pinning the Bun version for reproducible CI builds.

Using bun-version: latest in the Knip job may lead to inconsistent behavior if a new Bun version introduces breaking changes. The PR description mentions bun@1.3.10 as the project's packageManager version.

♻️ Proposed fix to pin Bun version
       - name: Setup Bun
         uses: oven-sh/setup-bun@v2
         with:
-          bun-version: latest
+          bun-version: '1.3.10'

Alternatively, you could read the version from package.json#packageManager to keep it in sync automatically.

📝 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
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
node-version: '24'
cache: 'npm'
bun-version: latest
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: '1.3.10'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/lint.yml around lines 126 - 129, The GitHub Actions step
"Setup Bun" currently sets bun-version: latest which risks non-reproducible CI;
change the step to pin a specific Bun version (e.g., bun@1.3.10) by setting
bun-version to that exact version string, or implement logic to read the Bun
version from package.json's packageManager field and pass it to the "Setup Bun"
step so the CI uses the project-specified version instead of latest.

Comment thread services/platform/Dockerfile Outdated
Comment on lines +7 to +8
# Bun binary for all stages
FROM oven/bun:latest AS bun-bin
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Pin the Bun image version for reproducible builds.

Using oven/bun:latest can cause unexpected behavior if the image updates. Pin to a specific version matching your packageManager field (1.3.10).

🔧 Proposed fix
-FROM oven/bun:latest AS bun-bin
+FROM oven/bun:1.3.10 AS bun-bin
📝 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
# Bun binary for all stages
FROM oven/bun:latest AS bun-bin
# Bun binary for all stages
FROM oven/bun:1.3.10 AS bun-bin
🧰 Tools
🪛 Checkov (3.2.334)

[low] 8-8: Ensure the base image uses a non latest version tag

(CKV_DOCKER_7)

🪛 Hadolint (2.14.0)

[warning] 8-8: Using latest is prone to errors if the image will ever update. Pin the version explicitly to a release tag

(DL3007)

🪛 Trivy (0.69.1)

[warning] 8-8: ':latest' tag used

Specify a tag in the 'FROM' statement for image 'oven/bun'

Rule: DS-0001

Learn more

(IaC/Dockerfile)

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

In `@services/platform/Dockerfile` around lines 7 - 8, The Dockerfile uses an
unpinned base image "FROM oven/bun:latest AS bun-bin" which can lead to
non-reproducible builds; change the FROM line to pin the Bun image to the
project packageManager version (e.g., use "oven/bun:1.3.10") so the bun-bin
stage consistently uses the same Bun release as declared in packageManager.

@@ -1,8 +1,8 @@
#!/usr/bin/env node
#!/usr/bin/env bun
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the actual file content
echo "=== File content ===" 
cat services/platform/scripts/generate-admin-password.mjs

echo ""
echo "=== Package.json bcryptjs version ===" 
if [ -f "services/platform/package.json" ]; then
  cat services/platform/package.json | jq '.dependencies.bcryptjs, .devDependencies.bcryptjs'
else
  echo "Package.json not found at services/platform/"
fi

# Check if bcryptjs is in a root package.json
if [ -f "package.json" ]; then
  echo ""
  echo "=== Root package.json bcryptjs version ===" 
  cat package.json | jq '.dependencies.bcryptjs, .devDependencies.bcryptjs'
fi

Repository: tale-project/tale

Length of output: 2098


🏁 Script executed:

# Check for native bindings in bcryptjs
echo "=== Checking for native bindings in bcryptjs ===" 
if [ -d "node_modules/bcryptjs" ]; then
  echo "bcryptjs found in node_modules"
  # Look for binding.gyp which indicates native addons
  if [ -f "node_modules/bcryptjs/binding.gyp" ]; then
    echo "Found binding.gyp - has native bindings"
  else
    echo "No binding.gyp found - appears to be pure JavaScript"
  fi
  # Check package.json for native build scripts
  cat node_modules/bcryptjs/package.json | jq '.scripts | keys'
else
  echo "bcryptjs not found in node_modules"
fi

Repository: tale-project/tale

Length of output: 144


🏁 Script executed:

# List the directory structure of bcryptjs to understand its nature
echo "=== bcryptjs directory structure ===" 
if [ -d "node_modules/bcryptjs" ]; then
  find node_modules/bcryptjs -type f -name "*.node" -o -name "*.gyp" 2>/dev/null | head -20
  echo "Main files in bcryptjs:"
  ls -la node_modules/bcryptjs/ | head -15
fi

Repository: tale-project/tale

Length of output: 97


🌐 Web query:

bcryptjs Bun compatibility pure JavaScript

💡 Result:

  • Yes—bcryptjs is Bun-compatible because it’s an optimized, zero-dependency, pure JavaScript bcrypt implementation (no native Node addons / node-gyp), so it runs anywhere JS runs (including Bun). (github.com)

  • In Bun, you can generally use it like:

    import bcrypt from "bcryptjs";
    const hash = await bcrypt.hash("pw", 10);
    const ok = await bcrypt.compare("pw", hash);

    (Async API available; sync variants exist.)

  • Tradeoff: being pure JS, it’s slower than native bindings (the project notes ~30% slower). (github.com)

  • Bun alternative: Bun has built-in password hashing/verification via Bun.password (including bcrypt) if you’d rather avoid a dependency. (bun.sh)

Citations:


bcryptjs is fully compatible with Bun—no action required.

bcryptjs is a pure JavaScript implementation with zero native dependencies, so it works seamlessly under Bun. The shebang change is appropriate, and the script will execute securely.

Alternatively, consider using Bun's built-in Bun.password API for bcrypt hashing to eliminate the dependency, though the current approach is perfectly valid and maintainable.

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

In `@services/platform/scripts/generate-admin-password.mjs` at line 1, No code
change is required—the bcryptjs usage and the new shebang in
generate-admin-password.mjs are valid under Bun; either leave the script as-is
or, if you want to remove the bcryptjs dependency, replace the bcryptjs import
and hash/compare calls with Bun's built-in API by deleting the bcryptjs
require/import and using Bun.password(...) to create the hash (and the
corresponding Bun verification method) in the functions that currently call
bcryptjs hashing/comparison.

Comment on lines +31 to +39
if (pathname !== '/') {
const filePath = resolve(distDir, pathname.slice(1));
if (filePath.startsWith(distDir)) {
const file = Bun.file(filePath);
if (await file.exists()) {
return new Response(file);
}
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Strengthen path traversal protection.

The resolve() function can normalize .. sequences, but consider using Bun.file().exists() check combined with realpath to handle symlinks and ensure the resolved path truly resides within distDir.

🛡️ Proposed improvement
     if (pathname !== '/') {
-      const filePath = resolve(distDir, pathname.slice(1));
-      if (filePath.startsWith(distDir)) {
+      const filePath = join(distDir, pathname.slice(1));
+      const resolved = resolve(filePath);
+      if (resolved.startsWith(distDir + '/') || resolved === distDir) {
         const file = Bun.file(filePath);
         if (await file.exists()) {
           return new Response(file);
         }
       }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/server.js` around lines 31 - 39, The current check using
resolve(pathname.slice(1)) and startsWith(distDir) can be bypassed by symlinks;
update the logic around filePath/Bun.file to also resolve real paths for both
the candidate file and distDir (e.g., use a realpath call) and compare those
canonical paths to ensure the file actually resides inside distDir, then verify
existence via Bun.file(...).exists() before returning the Response; reference
the variables pathname, distDir, filePath and the Bun.file existence check when
making this change.

Comment on lines +41 to +43
if (!indexHtmlTemplate) {
indexHtmlTemplate = await Bun.file(join(distDir, 'index.html')).text();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add error handling for missing index.html.

If dist/index.html doesn't exist (e.g., build wasn't run), the server will throw an unhandled error. Consider adding a graceful fallback.

🛡️ Proposed improvement
     if (!indexHtmlTemplate) {
-      indexHtmlTemplate = await Bun.file(join(distDir, 'index.html')).text();
+      const indexFile = Bun.file(join(distDir, 'index.html'));
+      if (!(await indexFile.exists())) {
+        return new Response('Build not found. Run `bun run build` first.', { status: 503 });
+      }
+      indexHtmlTemplate = await indexFile.text();
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/server.js` around lines 41 - 43, The code reads
indexHtmlTemplate from Bun.file(join(distDir, 'index.html')).text() without
handling a missing file; wrap that read in a try/catch (or check existence via
Bun.file(...).exists()) around the indexHtmlTemplate assignment in the server
startup logic so a missing dist/index.html doesn't throw; on failure log a clear
error including distDir and the filename, set indexHtmlTemplate to a small safe
fallback HTML (or null) and ensure request handlers return a 500 or the fallback
page if indexHtmlTemplate is absent to avoid unhandled exceptions (refer to
indexHtmlTemplate, Bun.file, join, distDir).

Replace Node.js/npm with Bun across the entire monorepo:

- Set packageManager to bun@1.3.10 in root package.json
- Replace all npm/npx commands with bun/bunx in scripts, CI, Docker
- Replace Express.js production server with native Bun.serve()
- Remove express and @types/express dependencies
- Delete package-lock.json, add bun.lock
- Delete .nvmrc files (no longer needed)
- Update GitHub Actions to use oven-sh/setup-bun@v2
- Update Dockerfile to use oven/bun:latest base image
- Update all shell scripts shebangs and commands
- Add package-lock.json to .gitignore
Bun runs TypeScript natively, so all scripts can be properly typed:

- scripts/dev.mjs → dev.ts
- scripts/sync-convex-env-from-dotenv.mjs → sync-convex-env-from-dotenv.ts
- scripts/encrypt-inline-secret.mjs → encrypt-inline-secret.ts
- scripts/generate-admin-password.mjs → generate-admin-password.ts
- server.js → server.ts
- .storybook/main.js → main.ts

Add @types/bun for Bun global type definitions.
Update all references in package.json, Dockerfile, and docker-entrypoint.sh.
Users can now upload connector.ts files in addition to connector.js.
TypeScript is transpiled to JavaScript at upload time using sucrase,
so the stored code and VM sandbox execution remain plain JS.
@yannickmonney yannickmonney force-pushed the chore/migrate-to-bun branch from 0a5ac32 to e62a76b Compare March 1, 2026 18:26
…cript

All 9 example connector files now use TypeScript with full sandbox API
type definitions. Tests transpile the .ts files via sucrase before
passing to the VM sandbox.
…es, a11y)

- Clean up knip config: add server.ts entry, remove stale ignores
- Unexport 5 types only used locally (TextFileCategory, EnvSetupOptions, etc.)
- Fix 27 no-shadow errors: rename shadowed variables across dialogs, convex
  mutations, stream buffer, error boundaries, and data table
- Memoize 6 context values with useMemo to fix jsx-no-constructed-context-values
- Cache error boundary context value in class component to avoid reconstruction
- Hoist story ability constants to module scope
- Fix a11y: use semantic button for folder names, use role="group" for drop zone
…e.json files

fix(FileUpload): change dropzone role from button to group for accessibility
@yannickmonney yannickmonney merged commit d7b0c45 into main Mar 1, 2026
16 checks passed
@yannickmonney yannickmonney deleted the chore/migrate-to-bun branch March 1, 2026 19:46
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.

1 participant