From 71cab7b93eaf027dcd0421a19a624ef8cbb50107 Mon Sep 17 00:00:00 2001 From: "lei.liao" Date: Wed, 28 Jan 2026 16:25:04 +0800 Subject: [PATCH 1/2] chore: release v1.1.36 (ensure bunx creation) --- install.ps1 | 2 +- install.sh | 2 +- package.json | 4 +- scripts/verify-e2e-npm.ts | 182 +++++++++++++++++++++------------- src/commands/install.ts | 21 +++- test/bunx_consistency.test.ts | 39 ++++++++ test/version_check.test.ts | 4 +- 7 files changed, 179 insertions(+), 75 deletions(-) create mode 100644 test/bunx_consistency.test.ts diff --git a/install.ps1 b/install.ps1 index 293d250..59b313d 100644 --- a/install.ps1 +++ b/install.ps1 @@ -39,7 +39,7 @@ function Detect-NetworkZone { $BVM_REGION = Detect-NetworkZone $REGISTRY = if ($BVM_REGION -eq "cn") { "registry.npmmirror.com" } else { "registry.npmjs.org" } -$DEFAULT_BVM_VER = "v1.1.35" +$DEFAULT_BVM_VER = "v1.1.36" $BVM_VER = if ($env:BVM_INSTALL_VERSION) { $env:BVM_INSTALL_VERSION } else { "" } if (-not $BVM_VER) { try { diff --git a/install.sh b/install.sh index 85ea722..a14633a 100755 --- a/install.sh +++ b/install.sh @@ -3,7 +3,7 @@ set -e # --- Configuration --- -DEFAULT_BVM_VERSION="v1.1.35" # Fallback +DEFAULT_BVM_VERSION="v1.1.36" # Fallback FALLBACK_BUN_VERSION="1.3.6" BVM_SRC_VERSION="${BVM_INSTALL_VERSION}" # If empty, will resolve dynamically diff --git a/package.json b/package.json index f5a0c61..90fd898 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "bvm-core", - "version": "1.1.35", - "description": "The native version manager for Bun. Cross-platform, shell-agnostic, and zero-dependency.", + "version": "1.1.36", + "description": "Bun Version Manager (BVM) - Native, fast, and cross-platform.", "main": "dist/index.js", "bin": { "bvm": "bin/bvm-npm.js" diff --git a/scripts/verify-e2e-npm.ts b/scripts/verify-e2e-npm.ts index 9717f77..f1da2fc 100644 --- a/scripts/verify-e2e-npm.ts +++ b/scripts/verify-e2e-npm.ts @@ -2,82 +2,130 @@ import { $ } from "bun"; import { join } from "path"; import { homedir } from "os"; -import { existsSync, readdirSync } from "fs"; +import { existsSync, readdirSync, mkdirSync, writeFileSync, chmodSync, rmSync } from "fs"; const HOME = homedir(); const BVM_DIR = join(HOME, ".bvm"); const SHIMS_DIR = join(BVM_DIR, "shims"); const BIN_DIR = join(BVM_DIR, "bin"); -console.log("\n🚀 Starting E2E NPM Verification Protocol...\n"); - -try { - // 1. Cleanup - console.log(`🧹 Cleaning up environment (${BVM_DIR})...`); - await $`rm -rf ${BVM_DIR}`; - await $`rm -f bvm-core-*.tgz`; - - // 2. Build & Pack - console.log("📦 Building and Packing..."); - // Using simple shell command to ensure npm run build executes correctly - await $`npm run build`.quiet(); - await $`npm pack`.quiet(); - - // Find the tarball - const files = readdirSync(process.cwd()); - const tarball = files.find(f => f.startsWith("bvm-core-") && f.endsWith(".tgz")); - if (!tarball) throw new Error("Tarball not found after packing!"); - console.log(`📝 Found tarball: ${tarball}`); - - // 3. Install - console.log("💿 Installing globally via NPM (this may take a moment)..."); - // We capture stdout/stderr to show it only on failure, or stream it? - // Let's stream it to be transparent as requested. - await $`npm install -g ./${tarball} --foreground-scripts --force`; - - // 4. Verify Physical Structure - console.log("🔍 Verifying filesystem structure..."); - - if (!existsSync(SHIMS_DIR)) throw new Error(`❌ Shims directory missing: ${SHIMS_DIR}`); - if (!existsSync(join(SHIMS_DIR, "bun"))) throw new Error(`❌ Bun shim missing`); - if (!existsSync(join(BIN_DIR, "bvm"))) throw new Error(`❌ BVM binary missing`); - - console.log(" ✅ Shims directory exists."); - console.log(" ✅ Bun shim exists."); - - // 5. Functional Verification - console.log("🧪 Verifying BVM functionality..."); - const bvmExec = join(BIN_DIR, "bvm"); - const output = await $`${bvmExec} ls`.text(); - console.log(output.trim()); - - if (!output.includes("Locally installed Bun versions")) { - throw new Error("❌ 'bvm ls' output is unexpected."); +export class E2ESandbox { + public bvmDir: string; + public shimsDir: string; + public binDir: string; + private tempHome: string; + + constructor() { + this.tempHome = join(process.cwd(), `.tmp-npm-verify-${Date.now()}`); + this.bvmDir = join(this.tempHome, ".bvm"); + this.shimsDir = join(this.bvmDir, "shims"); + this.binDir = join(this.bvmDir, "bin"); + if (!existsSync(this.tempHome)) mkdirSync(this.tempHome, { recursive: true }); + } + + async installLocal() { + // Mock implementation for testing safety/upgrade logic without full NPM network hit + if (!existsSync(this.bvmDir)) mkdirSync(this.bvmDir, { recursive: true }); + if (!existsSync(this.shimsDir)) mkdirSync(this.shimsDir, { recursive: true }); + if (!existsSync(this.binDir)) mkdirSync(this.binDir, { recursive: true }); + + const bvmBin = join(this.binDir, "bvm"); + const bvmSrcDir = join(this.bvmDir, "src"); + if (!existsSync(bvmSrcDir)) mkdirSync(bvmSrcDir, { recursive: true }); + + // Marker for NPM install + const marker = join(this.bvmDir, ".npm-install"); + writeFileSync(marker, "true"); + + // Dummy wrapper + writeFileSync(bvmBin, `#!/bin/bash\nexport BVM_INSTALL_SOURCE="npm"\nexec ${process.execPath} ${join(this.bvmDir, "src", "index.js")} "$@"\n`); + chmodSync(bvmBin, 0o755); + + // Dummy source + writeFileSync(join(bvmSrcDir, "index.js"), "import '...'; console.log('bvm')"); } - // 6. Global Package Isolation Verification (NEW) - console.log("📦 Verifying Global Package Isolation (Option B)..."); - // Ensure we are using a version - await $`${bvmExec} use default`.quiet(); - // Simulate bun install -g - console.log(" Installing dummy global package..."); - await $`${join(SHIMS_DIR, "bun")} install -g fake-pkg-test-bvm`.quiet().catch(() => {}); - - const currentBin = join(BVM_DIR, "current", "bin"); - console.log(` Checking if current bin path is correctly set up: ${currentBin}`); - - // We check if current/bin is in the PATH reported by setup - const zshrc = await $`cat ${join(HOME, ".zshrc")}`.text(); - if (!zshrc.includes("current/bin")) { - throw new Error("❌ .zshrc does not contain 'current/bin' in PATH"); + cleanup() { + if (existsSync(this.tempHome)) rmSync(this.tempHome, { recursive: true, force: true }); } +} + +async function runProtocol() { + console.log("\n🚀 Starting E2E NPM Verification Protocol...\n"); + + try { + // 1. Cleanup + console.log(`🧹 Cleaning up environment (${BVM_DIR})...`); + await $`rm -rf ${BVM_DIR}`; + await $`rm -f bvm-core-*.tgz`.nothrow(); // Use nothrow to avoid error if no tgz exists + + // 2. Build & Pack + console.log("📦 Building and Packing..."); + // Using simple shell command to ensure npm run build executes correctly + await $`npm run build`.quiet(); + await $`npm pack`.quiet(); + + // Find the tarball + const files = readdirSync(process.cwd()); + const tarball = files.find(f => f.startsWith("bvm-core-") && f.endsWith(".tgz")); + if (!tarball) throw new Error("Tarball not found after packing!"); + console.log(`📝 Found tarball: ${tarball}`); + + // 3. Install + console.log("💿 Installing globally via NPM (this may take a moment)..."); + // We capture stdout/stderr to show it only on failure, or stream it? + // Let's stream it to be transparent as requested. + await $`npm install -g ./${tarball} --foreground-scripts --force`; - console.log("\n✅ \x1b[32mE2E VERIFICATION PASSED!\x1b[0m"); - console.log(" BVM is installed, shims exist, and CLI works."); - console.log(` Run 'source ~/.zshrc' (or your shell config) to start using it.`); + // 4. Verify Physical Structure + console.log("🔍 Verifying filesystem structure..."); + + if (!existsSync(SHIMS_DIR)) throw new Error(`❌ Shims directory missing: ${SHIMS_DIR}`); + if (!existsSync(join(SHIMS_DIR, "bun"))) throw new Error(`❌ Bun shim missing`); + if (!existsSync(join(BIN_DIR, "bvm"))) throw new Error(`❌ BVM binary missing`); + + console.log(" ✅ Shims directory exists."); + console.log(" ✅ Bun shim exists."); + + // 5. Functional Verification + console.log("🧪 Verifying BVM functionality..."); + const bvmExec = join(BIN_DIR, "bvm"); + const output = await $`${bvmExec} ls`.text(); + console.log(output.trim()); + + if (!output.includes("Locally installed Bun versions")) { + throw new Error("❌ 'bvm ls' output is unexpected."); + } + + // 6. Global Package Isolation Verification (NEW) + console.log("📦 Verifying Global Package Isolation (Option B)..."); + // Ensure we are using a version + await $`${bvmExec} use default`.quiet(); + // Simulate bun install -g + console.log(" Installing dummy global package..."); + await $`${join(SHIMS_DIR, "bun")} install -g fake-pkg-test-bvm`.quiet().catch(() => {}); + + const currentBin = join(BVM_DIR, "current", "bin"); + console.log(` Checking if current bin path is correctly set up: ${currentBin}`); + + // We check if current/bin is in the PATH reported by setup + const zshrc = await $`cat ${join(HOME, ".zshrc")}`.text(); + if (!zshrc.includes("current/bin")) { + throw new Error("❌ .zshrc does not contain 'current/bin' in PATH"); + } + + console.log("\n✅ \x1b[32mE2E VERIFICATION PASSED!\x1b[0m"); + console.log(" BVM is installed, shims exist, and CLI works."); + console.log(` Run 'source ~/.zshrc' (or your shell config) to start using it.`); + + } catch (e) { + console.error("\n💥 \x1b[31mVERIFICATION FAILED:\x1b[0m"); + console.error(e); + process.exit(1); + } +} -} catch (e) { - console.error("\n💥 \x1b[31mVERIFICATION FAILED:\x1b[0m"); - console.error(e); - process.exit(1); +// Check if this script is being run directly +if (import.meta.main) { + runProtocol(); } diff --git a/src/commands/install.ts b/src/commands/install.ts index 4c92f7b..309f563 100644 --- a/src/commands/install.ts +++ b/src/commands/install.ts @@ -4,7 +4,7 @@ import { ensureDir, pathExists, removeDir, resolveVersion, normalizeVersion, rea import { findBunDownloadUrl, fetchBunVersions, checkBunVersionExists, fetchBunDistTags } from '../api'; import { colors, ProgressBar } from '../utils/ui'; import { extractArchive } from '../utils/archive'; -import { chmod } from 'fs/promises'; +import { chmod, rename, rm, symlink, unlink } from 'fs/promises'; import { configureShell } from './setup'; import { getRcVersion } from '../rc'; import { getInstalledVersions } from '../utils'; @@ -13,10 +13,23 @@ import { withSpinner } from '../command-runner'; import { runCommand } from '../helpers/process'; import { useBunVersion } from './use'; import { rehash } from './rehash'; -import { rename, rm } from 'fs/promises'; import { RegistrySpeedTester, REGISTRIES } from '../utils/registry-check'; import { BunfigManager } from '../utils/bunfig'; +async function ensureBunx(binDir: string, bunPath: string) { + const bunxName = EXECUTABLE_NAME.replace('bun', 'bunx'); + const bunxPath = join(binDir, bunxName); + + if (await pathExists(bunxPath)) return; + + try { + await symlink(EXECUTABLE_NAME, bunxPath); + } catch (e) { + await Bun.write(Bun.file(bunxPath), Bun.file(bunPath)); + await chmod(bunxPath, 0o755); + } +} + async function safeRename(src: string, dest: string) { try { await rename(src, dest); @@ -165,6 +178,7 @@ export async function installBunVersion(targetVersion?: string, options: { globa if (await pathExists(bunExecutablePath)) { spinner.succeed(colors.green(`Bun ${foundVersion} is already installed.`)); + await ensureBunx(installBinDir, bunExecutablePath); installedVersion = foundVersion; shouldConfigureShell = true; } else { @@ -183,11 +197,13 @@ export async function installBunVersion(targetVersion?: string, options: { globa await chmod(bunExecutablePath, 0o755); } spinner.succeed(colors.green(`Bun ${foundVersion} linked from local runtime.`)); + await ensureBunx(installBinDir, bunExecutablePath); installedVersion = foundVersion; shouldConfigureShell = true; } else if (IS_TEST_MODE) { await ensureDir(installBinDir); await writeTestBunBinary(bunExecutablePath, foundVersion); + await ensureBunx(installBinDir, bunExecutablePath); installedVersion = foundVersion; shouldConfigureShell = true; } else { @@ -261,6 +277,7 @@ Debug: ${error.message}`)); // Visible if spinner fails } await chmod(bunExecutablePath, 0o755); spinner.succeed(colors.green(`Bun ${foundVersion} installed successfully.`)); + await ensureBunx(installBinDir, bunExecutablePath); installedVersion = foundVersion; shouldConfigureShell = true; } diff --git a/test/bunx_consistency.test.ts b/test/bunx_consistency.test.ts new file mode 100644 index 0000000..f5f6645 --- /dev/null +++ b/test/bunx_consistency.test.ts @@ -0,0 +1,39 @@ +import { describe, it, expect, beforeEach, afterEach } from 'bun:test'; +import { join } from 'path'; +import { existsSync, mkdirSync, rmSync, readlinkSync } from 'fs'; +import { tmpdir } from 'os'; +import { BVM_VERSIONS_DIR, EXECUTABLE_NAME } from '../src/constants'; + +// We mock the environment to test install logic in isolation if possible, +// but since install.ts depends on many things, we'll test the shim generation and bunx presence logic. + +describe('Bunx Consistency', () => { + it('should ensure bunx exists in version bin directory', async () => { + // This is a unit-test level check of the logic we added to install.ts + // Since we can't easily run the full installBunVersion without network, + // we'll verify the helper function or the outcome if we were to run it. + + // Let's use the actual built dist to run a controlled install if possible, + // but for speed, let's just verify that rehash picks up bunx. + + const testBvmDir = join(tmpdir(), `bvm-bunx-test-${Date.now()}`); + const versionDir = join(testBvmDir, 'versions', 'v1.3.6'); + const binDir = join(versionDir, 'bin'); + mkdirSync(binDir, { recursive: true }); + + const bunPath = join(binDir, 'bun'); + const bunxPath = join(binDir, 'bunx'); + + await Bun.write(bunPath, '#!/bin/sh\necho 1.3.6'); + + // Simulate the logic in install.ts: + if (!existsSync(bunxPath)) { + await require('fs/promises').symlink('bun', bunxPath); + } + + expect(existsSync(bunxPath)).toBe(true); + expect(readlinkSync(bunxPath)).toBe('bun'); + + rmSync(testBvmDir, { recursive: true, force: true }); + }); +}); diff --git a/test/version_check.test.ts b/test/version_check.test.ts index cb067b5..c34e4e7 100644 --- a/test/version_check.test.ts +++ b/test/version_check.test.ts @@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test"; import packageJson from "../package.json"; describe("Version Check", () => { - test("package.json version should be 1.1.34", () => { - expect(packageJson.version).toBe("1.1.34"); + test("package.json version should be 1.1.36", () => { + expect(packageJson.version).toBe("1.1.36"); }); }); From 51b3a0bb511f379901a7005281380a9e374fff90 Mon Sep 17 00:00:00 2001 From: "lei.liao" Date: Thu, 29 Jan 2026 11:47:42 +0800 Subject: [PATCH 2/2] fix(win): fix bunx command and resolve code duplication in shims - Fix bunx.cmd to correctly use fast-path and add fallback to 'bun x' - Fix bun.cmd fast-path to point to user current version instead of runtime - Update rehash logic to use proper templates for bun/bunx shims on Windows - Improve bvm-shim.js to handle missing bunx.exe with fallback - Update install scripts to ensure bunx availability --- conductor/tracks.md | 2 +- .../tracks/fix_windows_bunx_20260128/index.md | 5 ++++ .../fix_windows_bunx_20260128/metadata.json | 8 ++++++ .../tracks/fix_windows_bunx_20260128/plan.md | 24 +++++++++++++++++ .../tracks/fix_windows_bunx_20260128/spec.md | 27 +++++++++++++++++++ install.ps1 | 4 ++- install.sh | 2 ++ src/commands/rehash.ts | 13 ++++++++- src/templates/win/bun.cmd | 1 - src/templates/win/bunx.cmd | 11 +++++--- src/templates/win/bvm-shim.js | 21 ++++++++++++--- 11 files changed, 107 insertions(+), 11 deletions(-) create mode 100644 conductor/tracks/fix_windows_bunx_20260128/index.md create mode 100644 conductor/tracks/fix_windows_bunx_20260128/metadata.json create mode 100644 conductor/tracks/fix_windows_bunx_20260128/plan.md create mode 100644 conductor/tracks/fix_windows_bunx_20260128/spec.md diff --git a/conductor/tracks.md b/conductor/tracks.md index 41ba5df..7a7f0e5 100644 --- a/conductor/tracks.md +++ b/conductor/tracks.md @@ -4,7 +4,7 @@ This file tracks all major tracks for the project. Each track has its own detail --- -## [ ] Track: 增强 Shim 解析逻辑与项目上下文检测 +## [~] Track: 增强 Shim 解析逻辑与项目上下文检测 *Link: [./conductor/tracks/shim_context_20251222/](./conductor/tracks/shim_context_20251222/)* diff --git a/conductor/tracks/fix_windows_bunx_20260128/index.md b/conductor/tracks/fix_windows_bunx_20260128/index.md new file mode 100644 index 0000000..30b446b --- /dev/null +++ b/conductor/tracks/fix_windows_bunx_20260128/index.md @@ -0,0 +1,5 @@ +# Track fix_windows_bunx_20260128 Context + +- [Specification](./spec.md) +- [Implementation Plan](./plan.md) +- [Metadata](./metadata.json) diff --git a/conductor/tracks/fix_windows_bunx_20260128/metadata.json b/conductor/tracks/fix_windows_bunx_20260128/metadata.json new file mode 100644 index 0000000..1c009a1 --- /dev/null +++ b/conductor/tracks/fix_windows_bunx_20260128/metadata.json @@ -0,0 +1,8 @@ +{ + "track_id": "fix_windows_bunx_20260128", + "type": "bug", + "status": "new", + "created_at": "2026-01-28T10:00:00Z", + "updated_at": "2026-01-28T10:00:00Z", + "description": "修复 BVM 在 Windows 环境下 bunx 命令失效的问题,主要涉及身份识别和显式路由优化。" +} diff --git a/conductor/tracks/fix_windows_bunx_20260128/plan.md b/conductor/tracks/fix_windows_bunx_20260128/plan.md new file mode 100644 index 0000000..4516227 --- /dev/null +++ b/conductor/tracks/fix_windows_bunx_20260128/plan.md @@ -0,0 +1,24 @@ +### Implementation Plan: fix_windows_bunx_20260128 + +#### Phase 1: Core Shim & Template Optimization +*目标:在核心代码层面修复身份识别和路由逻辑。* + +- [ ] Task: 更新 Windows JS Shim 模板 (`src/templates/win/bvm-shim.js`),实现 `bunx` 到 `bun x` 的显式指令路由。 [x] +- [ ] Task: 更新 Windows CMD 模板 (`src/templates/win/bunx.cmd` & `bun.cmd`),为快速通道添加 `x` 路由支持。 [x] +- [ ] Task: 优化 Unix Shim 模板 (`src/templates/unix/bvm-shim.sh`),使用 `exec -a` 增强身份识别稳定性。 [x] +- [ ] Task: Conductor - User Manual Verification 'Phase 1: Core Shim & Template Optimization' (Protocol in workflow.md) + +#### Phase 2: Installer & Self-Repair Evolution +*目标:确保新安装及现有环境均能自动补全所需的物理文件。* + +- [ ] Task: 改进 `install.ps1` (Windows),增加 `bunx.exe` 副本生成逻辑及旧版本扫描修复。 [x] +- [ ] Task: 改进 `install.sh` (Unix),完善运行时下载后的 `bunx` 软链接创建。 [x] +- [ ] Task: 更新 `src/commands/setup.ts`,确保运行 `bvm setup` 时能递归修复所有已安装版本的 `bunx` 文件。 [ ] +- [ ] Task: Conductor - User Manual Verification 'Phase 2: Installer & Self-Repair Evolution' (Protocol in workflow.md) + +#### Phase 3: Verification & TDD +*目标:通过测试验证修复效果,并确保发布版本同步。* + +- [ ] Task: 编写或更新 `test/bunx_consistency.test.ts`,在模拟环境下验证 Windows 路由和文件补全。 [ ] +- [ ] Task: 执行 `bun run build` 同步所有安装脚本版本,并运行全量测试。 [x] +- [ ] Task: Conductor - User Manual Verification 'Phase 3: Verification & TDD' (Protocol in workflow.md) diff --git a/conductor/tracks/fix_windows_bunx_20260128/spec.md b/conductor/tracks/fix_windows_bunx_20260128/spec.md new file mode 100644 index 0000000..4957211 --- /dev/null +++ b/conductor/tracks/fix_windows_bunx_20260128/spec.md @@ -0,0 +1,27 @@ +### Track Specification: fix_windows_bunx_20260128 + +#### Overview +修复 BVM 在 Windows 环境下 `bunx` 命令失效的问题。 + +**当前问题证据(用户截图/日志):** +```powershell +PS C:\Users\steph\OneDrive\Desktop> bunx +Bun is a fast JavaScript runtime... (输出了帮助信息,未识别为 bunx) +PS C:\Users\steph\OneDrive\Desktop> bunx skills add microsoft/PowerToys +error: Script not found "skills" (身份识别错误导致路由到了 bun run 逻辑) +``` + +#### Functional Requirements +1. **显式路由逻辑**:修改 Windows 垫片(Shim)逻辑,将 `bunx ` 显式路由为 `bun x `,确保 100% 触发 Bun 的扩展包运行逻辑。 +2. **物理文件补全**:在安装 Bun 运行时或执行 `bvm setup` 时,确保版本 bin 目录下存在 `bunx.exe`。 +3. **安装脚本自愈**: + * 更新 `install.ps1`,在安装新版本或检测到系统 Bun 时,自动补全缺失的 `bunx.exe`。 + * 更新 `install.sh`,同步确保 Unix 环境下的 `bunx` 软链接正确。 +4. **环境一键修复**:通过 `npm i . -g` 或 `bvm setup` 即可自动修复所有已安装版本的 `bunx` 坏道。 +5. **版本切换支持**:`bunx` 必须严格遵循 `.bvmrc` 或全局默认版本的切换逻辑。 + +#### Acceptance Criteria +- [ ] **身份验证**:在 Windows 中运行 `bunx --version` 返回版本号,不再返回 Bun 的通用帮助信息。 +- [ ] **功能验证**:运行 `bunx skills add ...` 能正确识别 `skills` 为包名并执行,不再报 "Script not found"。 +- [ ] **物理验证**:重新运行 `bvm setup` 后,所有已安装版本的 `bin` 目录下均出现 `bunx` 文件。 +- [ ] **安装验证**:运行 `install.sh` 或 `install.ps1` 完成后,新安装的版本自带可运行的 `bunx`。 diff --git a/install.ps1 b/install.ps1 index 59b313d..7790463 100644 --- a/install.ps1 +++ b/install.ps1 @@ -97,6 +97,7 @@ if ($SYSTEM_BUN_BIN) { $SYS_BIN_DIR = Join-Path $SYS_VER_DIR "bin" if (-not (Test-Path $SYS_BIN_DIR)) { New-Item -ItemType Directory -Path $SYS_BIN_DIR -Force | Out-Null } Copy-Item $SYSTEM_BUN_BIN (Join-Path $SYS_BIN_DIR "bun.exe") -Force + Copy-Item $SYSTEM_BUN_BIN (Join-Path $SYS_BIN_DIR "bunx.exe") -Force # Smoke Test $BvmIndex = Join-Path $BVM_SRC_DIR "index.js" @@ -129,6 +130,7 @@ if ($USE_SYSTEM_AS_RUNTIME) { $BIN_DEST = Join-Path $TARGET_DIR "bin" if (-not (Test-Path $BIN_DEST)) { New-Item -ItemType Directory -Path $BIN_DEST -Force | Out-Null } Move-Item -Path $FoundBun.FullName -Destination (Join-Path $BIN_DEST "bun.exe") -Force + Copy-Item (Join-Path $BIN_DEST "bun.exe") (Join-Path $BIN_DEST "bunx.exe") -Force Remove-Item $TMP -Force Remove-Item $EXT -Recurse -Force } @@ -163,7 +165,7 @@ set "BVM_DIR=$WinBvmDir" set "BUN_INSTALL=%BVM_DIR%\current" if not exist ".bvmrc" ( - "%BVM_DIR%\runtime\current\bin\bun.exe" %* + "%BVM_DIR%\runtime\current\bin\%~n0.exe" %* exit /b %errorlevel% ) diff --git a/install.sh b/install.sh index a14633a..e67c2bf 100755 --- a/install.sh +++ b/install.sh @@ -153,6 +153,7 @@ if [ -n "$SYSTEM_BUN_BIN" ]; then cp "$SYSTEM_BUN_BIN" "${SYS_VER_DIR}/bin/bun" fi chmod +x "${SYS_VER_DIR}/bin/bun" + ln -sf "./bun" "${SYS_VER_DIR}/bin/bunx" # Smoke Test if "${SYS_VER_DIR}/bin/bun" "${BVM_SRC_DIR}/index.js" --version >/dev/null 2>&1; then @@ -206,6 +207,7 @@ else mkdir -p "${TARGET_RUNTIME_DIR}/bin" mv "$(find "$TEMP_DIR_BUN" -type f -name "bun" | head -n 1)" "${TARGET_RUNTIME_DIR}/bin/bun" chmod +x "${TARGET_RUNTIME_DIR}/bin/bun" + ln -sf "./bun" "${TARGET_RUNTIME_DIR}/bin/bunx" rm -rf "$TEMP_DIR_BUN" fi fi diff --git a/src/commands/rehash.ts b/src/commands/rehash.ts index 2f08148..35afe61 100644 --- a/src/commands/rehash.ts +++ b/src/commands/rehash.ts @@ -4,6 +4,11 @@ import { BVM_SHIMS_DIR, BVM_VERSIONS_DIR, BVM_DIR, BVM_BIN_DIR, OS_PLATFORM, EXE import { ensureDir, pathExists, readDir } from '../utils'; import { colors } from '../utils/ui'; +import { + BVM_BUN_CMD_TEMPLATE, + BVM_BUNX_CMD_TEMPLATE +} from '../templates/init-scripts'; + /** * Rehash command: regenerates all shims based on installed Bun versions. */ @@ -68,7 +73,13 @@ export async function rehash() { // 3. Generate Wrappers for (const bin of executables) { if (isWindows) { - await Bun.write(join(BVM_SHIMS_DIR, `${bin}.cmd`), WRAPPER_CMD(bin)); + if (bin === 'bun') { + await Bun.write(join(BVM_SHIMS_DIR, 'bun.cmd'), BVM_BUN_CMD_TEMPLATE); + } else if (bin === 'bunx') { + await Bun.write(join(BVM_SHIMS_DIR, 'bunx.cmd'), BVM_BUNX_CMD_TEMPLATE); + } else { + await Bun.write(join(BVM_SHIMS_DIR, `${bin}.cmd`), WRAPPER_CMD(bin)); + } const ps1 = join(BVM_SHIMS_DIR, `${bin}.ps1`); if (await pathExists(ps1)) await unlink(ps1); } else { diff --git a/src/templates/win/bun.cmd b/src/templates/win/bun.cmd index 9310ed2..f25449f 100644 --- a/src/templates/win/bun.cmd +++ b/src/templates/win/bun.cmd @@ -10,4 +10,3 @@ if not exist ".bvmrc" ( :: Slow-path: Hand over to JS shim for version resolution "%BVM_DIR%\runtime\current\bin\bun.exe" "%BVM_DIR%\bin\bvm-shim.js" "bun" %* - diff --git a/src/templates/win/bunx.cmd b/src/templates/win/bunx.cmd index 1a86cda..95670a8 100644 --- a/src/templates/win/bunx.cmd +++ b/src/templates/win/bunx.cmd @@ -2,10 +2,15 @@ set "BVM_DIR=%USERPROFILE%\.bvm" set "BUN_INSTALL=%BVM_DIR%\current" +:: Fast-path: If no .bvmrc in current directory, run default directly if not exist ".bvmrc" ( - "%BVM_DIR%\runtime\current\bin\bun.exe" %* + if exist "%BVM_DIR%\current\bin\bunx.exe" ( + "%BVM_DIR%\current\bin\bunx.exe" %* + ) else ( + "%BVM_DIR%\current\bin\bun.exe" x %* + ) exit /b %errorlevel% ) -"%BVM_DIR%\runtime\current\bin\bun.exe" "%BVM_DIR%\bin\bvm-shim.js" "bunx" %* - +:: Slow-path: Hand over to JS shim for version resolution +"%BVM_DIR%\runtime\current\bin\bun.exe" "%BVM_DIR%\bin\bvm-shim.js" "bunx" %* \ No newline at end of file diff --git a/src/templates/win/bvm-shim.js b/src/templates/win/bvm-shim.js index 97ebc0e..8c32725 100644 --- a/src/templates/win/bvm-shim.js +++ b/src/templates/win/bvm-shim.js @@ -59,17 +59,30 @@ if (!version) { const versionDir = path.join(BVM_DIR, 'versions', version); const binDir = path.join(versionDir, 'bin'); -const realExecutable = path.join(binDir, CMD + '.exe'); +let realExecutable = path.join(binDir, CMD + '.exe'); +let finalArgs = ARGS; if (!fs.existsSync(realExecutable)) { - console.error("BVM Error: Command '" + CMD + "' not found in Bun " + version + " at " + realExecutable); - process.exit(127); + if (CMD === 'bunx') { + // Fallback: Use 'bun.exe x' if 'bunx.exe' is missing + const bunExe = path.join(binDir, 'bun.exe'); + if (fs.existsSync(bunExe)) { + realExecutable = bunExe; + finalArgs = ['x', ...ARGS]; + } else { + console.error("BVM Error: Both 'bunx.exe' and 'bun.exe' are missing in Bun " + version); + process.exit(127); + } + } else { + console.error("BVM Error: Command '" + CMD + "' not found in Bun " + version + " at " + realExecutable); + process.exit(127); + } } process.env.BUN_INSTALL = versionDir; process.env.PATH = binDir + path.delimiter + process.env.PATH; -const child = spawn(realExecutable, ARGS, { stdio: 'inherit', shell: false }); +const child = spawn(realExecutable, finalArgs, { stdio: 'inherit', shell: false }); child.on('exit', (code) => { if (code === 0 && (CMD === 'bun' || CMD === 'bunx')) { const isGlobal = ARGS.includes('-g') || ARGS.includes('--global');