Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/fix-bun-global-install-detection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---

Fix install-source detection for bun global installs by recognizing the `~/.bun/node_modules/` layout used by recent bun versions.
6 changes: 4 additions & 2 deletions apps/kimi-code/src/cli/update/source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export function detectNativeInstall(): boolean {
// Path heuristic markers (compared in lowercase; both forward and backward slashes accepted).
const PNPM_PATH_SEGMENT = 'pnpm/global/';
const YARN_PATH_SEGMENTS = ['.config/yarn/global/', '/.yarn/global/'];
const BUN_PATH_SEGMENT = '.bun/install/global/';
const BUN_PATH_SEGMENTS = ['/.bun/install/global/', '/.bun/node_modules/'];
// Homebrew installs formulae under its Cellar directory. Avoid matching the
// broader /homebrew/ prefix — on Apple Silicon, npm itself lives under
// /opt/homebrew/, so `npm install -g` paths also contain /homebrew/.
Expand All @@ -60,7 +60,9 @@ export function classifyByPathHeuristic(packageRoot: string): InstallSource | nu
for (const seg of YARN_PATH_SEGMENTS) {
if (normalized.includes(seg)) return 'yarn-global';
}
if (normalized.includes(BUN_PATH_SEGMENT)) return 'bun-global';
for (const seg of BUN_PATH_SEGMENTS) {
if (normalized.includes(seg)) return 'bun-global';
}
if (normalized.includes(HOMEBREW_PATH_SEGMENT)) return 'homebrew';
return null;
}
Expand Down
27 changes: 25 additions & 2 deletions apps/kimi-code/test/cli/update/source.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,24 @@ describe('classifyByPathHeuristic', () => {
).toBe('yarn-global');
});

it('detects bun global', () => {
it('detects bun global (install/global layout)', () => {
expect(
classifyByPathHeuristic('/Users/me/.bun/install/global/node_modules/@moonshot-ai/kimi-code'),
).toBe('bun-global');
});

it('detects bun global (node_modules layout)', () => {
expect(
classifyByPathHeuristic('/Users/me/.bun/node_modules/@moonshot-ai/kimi-code'),
).toBe('bun-global');
});

it('does not treat a local project named foo.bun as bun global', () => {
expect(
classifyByPathHeuristic('/work/foo.bun/node_modules/@moonshot-ai/kimi-code'),
).toBeNull();
});

it('detects homebrew on macOS (Cellar path)', () => {
expect(
classifyByPathHeuristic('/opt/homebrew/Cellar/kimi-code/0.5.0/libexec/lib/node_modules/@moonshot-ai/kimi-code'),
Expand Down Expand Up @@ -108,7 +120,7 @@ describe('detectInstallSource', () => {
).resolves.toBe('yarn-global');
});

it('returns bun-global when packageRoot matches bun heuristic', async () => {
it('returns bun-global when packageRoot matches bun heuristic (install/global layout)', async () => {
await expect(
detectInstallSource({
getPackageRoot: () => '/Users/me/.bun/install/global/node_modules/@moonshot-ai/kimi-code',
Expand All @@ -119,6 +131,17 @@ describe('detectInstallSource', () => {
).resolves.toBe('bun-global');
});

it('returns bun-global when packageRoot matches bun heuristic (node_modules layout)', async () => {
await expect(
detectInstallSource({
getPackageRoot: () => '/Users/me/.bun/node_modules/@moonshot-ai/kimi-code',
getGlobalPrefix: async () => '/usr/local',
detectNative: () => false,
platform: 'darwin',
}),
).resolves.toBe('bun-global');
});

it('returns npm-global when packageRoot matches npm prefix', async () => {
await expect(
detectInstallSource({
Expand Down