Skip to content

Commit 2f247d8

Browse files
committed
Bundle ripgrep, write it in the same directory as codebuff bin at runtime
1 parent c5dd269 commit 2f247d8

File tree

4 files changed

+91
-3
lines changed

4 files changed

+91
-3
lines changed

cli/src/hooks/use-connection-status.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export const useConnectionStatus = () => {
5353
}
5454

5555
const checkConnection = async () => {
56-
const client = getCodebuffClient()
56+
const client = await getCodebuffClient()
5757
if (!client) {
5858
if (isMounted) {
5959
setIsConnected(false)

cli/src/hooks/use-send-message.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -531,7 +531,7 @@ export const useSendMessage = ({
531531
setInputFocused(true)
532532
inputRef.current?.focus()
533533

534-
const client = getCodebuffClient()
534+
const client = await getCodebuffClient()
535535

536536
if (!client) {
537537
logger.error(

cli/src/native/ripgrep.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import path from 'path'
2+
3+
import { getBundledRgPath } from '@codebuff/sdk'
4+
import { spawnSync } from 'bun'
5+
6+
import { logger } from '../utils/logger'
7+
8+
const getRipgrepPath = async (): Promise<string> => {
9+
// In dev mode, use the SDK's bundled ripgrep binary
10+
if (!process.env.CODEBUFF_IS_BINARY) {
11+
return getBundledRgPath()
12+
}
13+
14+
// Compiled mode - self-extract the embedded binary to the same directory as the current binary
15+
const binaryDir = path.dirname(process.execPath)
16+
const rgFileName = process.platform === 'win32' ? 'rg.exe' : 'rg'
17+
const outPath = path.join(binaryDir, rgFileName)
18+
19+
// Check if already extracted
20+
if (await Bun.file(outPath).exists()) {
21+
return outPath
22+
}
23+
24+
// Extract the embedded binary
25+
try {
26+
// Use require() with literal paths to ensure the binary gets bundled into the compiled CLI
27+
// This is necessary for Bun's binary compilation to include the ripgrep binary
28+
let embeddedRgPath: string
29+
30+
if (process.platform === 'darwin' && process.arch === 'arm64') {
31+
embeddedRgPath = require('../../../sdk/dist/vendor/ripgrep/arm64-darwin/rg')
32+
} else if (process.platform === 'darwin' && process.arch === 'x64') {
33+
embeddedRgPath = require('../../../sdk/dist/vendor/ripgrep/x64-darwin/rg')
34+
} else if (process.platform === 'linux' && process.arch === 'arm64') {
35+
embeddedRgPath = require('../../../sdk/dist/vendor/ripgrep/arm64-linux/rg')
36+
} else if (process.platform === 'linux' && process.arch === 'x64') {
37+
embeddedRgPath = require('../../../sdk/dist/vendor/ripgrep/x64-linux/rg')
38+
} else if (process.platform === 'win32' && process.arch === 'x64') {
39+
embeddedRgPath = require('../../../sdk/dist/vendor/ripgrep/x64-win32/rg.exe')
40+
} else {
41+
throw new Error(`Unsupported platform: ${process.platform}-${process.arch}`)
42+
}
43+
44+
// Copy SDK's bundled binary to binary directory for portability
45+
await Bun.write(outPath, await Bun.file(embeddedRgPath).arrayBuffer())
46+
47+
// Make executable on Unix systems
48+
if (process.platform !== 'win32') {
49+
spawnSync(['chmod', '+x', outPath])
50+
}
51+
52+
return outPath
53+
} catch (error) {
54+
logger.error({ error }, 'Failed to extract ripgrep binary')
55+
// Fallback to SDK's bundled ripgrep if extraction fails
56+
return getBundledRgPath()
57+
}
58+
}
59+
60+
// Cache the promise to avoid multiple extractions
61+
let rgPathPromise: Promise<string> | null = null
62+
63+
export const getRgPath = (): Promise<string> => {
64+
if (!rgPathPromise) {
65+
rgPathPromise = getRipgrepPath()
66+
}
67+
return rgPathPromise
68+
}
69+
70+
/**
71+
* Reset the cached ripgrep path promise.
72+
* Used primarily for testing to force re-extraction.
73+
*/
74+
export const resetRgPathCache = (): void => {
75+
rgPathPromise = null
76+
}

cli/src/utils/codebuff-client.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { getAuthTokenDetails } from './auth'
55
import { loadAgentDefinitions } from './load-agent-definitions'
66
import { logger } from './logger'
77
import { getProjectRoot } from '../project-files'
8+
import { getRgPath } from '../native/ripgrep'
89

910
let clientInstance: CodebuffClient | null = null
1011

@@ -16,7 +17,7 @@ export function resetCodebuffClient(): void {
1617
clientInstance = null
1718
}
1819

19-
export function getCodebuffClient(): CodebuffClient | null {
20+
export async function getCodebuffClient(): Promise<CodebuffClient | null> {
2021
if (!clientInstance) {
2122
const { token: apiKey, source } = getAuthTokenDetails()
2223

@@ -29,6 +30,17 @@ export function getCodebuffClient(): CodebuffClient | null {
2930
}
3031

3132
const projectRoot = getProjectRoot()
33+
34+
// Set up ripgrep path for SDK to use
35+
if (process.env.CODEBUFF_IS_BINARY) {
36+
try {
37+
const rgPath = await getRgPath()
38+
process.env.CODEBUFF_RG_PATH = rgPath
39+
} catch (error) {
40+
logger.error(error, 'Failed to set up ripgrep binary for SDK')
41+
}
42+
}
43+
3244
try {
3345
const agentDefinitions = loadAgentDefinitions()
3446
clientInstance = new CodebuffClient({

0 commit comments

Comments
 (0)