-
Notifications
You must be signed in to change notification settings - Fork 15.5k
feat: 将 ripgrep 二进制文件通过 base64 编码嵌入到编译产物 #334
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,198 @@ | ||
| import { getMacroDefines } from "./scripts/defines.ts"; | ||
| import { exit } from "process"; | ||
| import { join, resolve } from "path"; | ||
| import { readFile, writeFile } from "fs/promises"; | ||
|
|
||
| const outfile = process.platform === "win32" ? "claude.exe" : "claude"; | ||
|
|
||
| // Use the currently running bun executable | ||
| const bunExe = process.execPath; | ||
|
|
||
| // Collect FEATURE_* env vars from environment | ||
| const features = Object.keys(process.env) | ||
| .filter(k => k.startsWith("FEATURE_")) | ||
| .map(k => k.replace("FEATURE_", "")); | ||
|
|
||
| // Auto-enable CHICAGO_MCP so @ant packages (computer-use-mcp, etc.) | ||
| // are bundled into the standalone exe. Without this flag, the feature-gated | ||
| // dynamic imports are tree-shaken and the native .node files are not embedded. | ||
| if (!features.includes("CHICAGO_MCP")) { | ||
| features.push("CHICAGO_MCP"); | ||
| } | ||
|
|
||
| const defines = getMacroDefines(); | ||
|
|
||
| // Build --define flags | ||
| const defineArgs = Object.entries(defines).flatMap(([k, v]) => [ | ||
| "--define", | ||
| `${k}:${v}`, | ||
| ]); | ||
|
|
||
| // Pass BUNDLED_MODE flag so ripgrepAsset.ts knows we're in compiled mode | ||
| const defineArgsWithBundled = [ | ||
| ...defineArgs, | ||
| "--define", | ||
| `BUNDLED_MODE:"true"`, | ||
| ]; | ||
|
|
||
| // Build --feature flags | ||
| const featureArgs = features.flatMap(f => ["--feature", f]); | ||
|
|
||
| // ─── Native module embedding ────────────────────────────────────────────────── | ||
| // bun build --compile embeds .node files as assets. When the bundler sees | ||
| // process.env.XXX_NODE_PATH with the var set to an absolute .node path, | ||
| // it rewrites the string to the bunfs asset path. This lets the runtime | ||
| // require() the embedded .node from within the compiled exe. | ||
| // | ||
| // Paths must use forward slashes and be absolute at compile time. | ||
| const repoRoot = resolve(__dirname); | ||
|
|
||
| const nativeNodePaths: Record<string, string> = { | ||
| // @ant packages — macOS only. Path is used at compile time for Bun asset embedding. | ||
| // Runtime: TS files check process.platform !== "darwin" and skip native load. | ||
| COMPUTER_USE_INPUT_NODE_PATH: join(repoRoot, | ||
| "packages/@ant/computer-use-input/prebuilds/arm64-darwin/computer-use-input.node"), | ||
| COMPUTER_USE_SWIFT_NODE_PATH: join(repoRoot, | ||
| "packages/@ant/computer-use-swift/prebuilds/arm64-darwin/computer_use.node"), | ||
|
|
||
| // vendor modules — cross-platform (win32/linux/darwin) | ||
| AUDIO_CAPTURE_NODE_PATH: join(repoRoot, | ||
| `vendor/audio-capture/${process.arch}-${process.platform}/audio-capture.node`), | ||
| IMAGE_PROCESSOR_NODE_PATH: join(repoRoot, | ||
| `vendor/image-processor/${process.arch}-${process.platform}/image-processor.node`), | ||
| // modifiers and url-handler are macOS only — paths point to darwin builds | ||
| MODIFIERS_NODE_PATH: join(repoRoot, | ||
| `vendor/modifiers-napi/${process.arch}-darwin/modifiers.node`), | ||
| URL_HANDLER_NODE_PATH: join(repoRoot, | ||
| `vendor/url-handler/${process.arch}-darwin/url-handler.node`), | ||
| }; | ||
|
|
||
| // Build env with native paths (forward slashes for Bun compatibility) | ||
| const compileEnv: Record<string, string> = { | ||
| ...process.env, | ||
| ...Object.fromEntries( | ||
| Object.entries(nativeNodePaths).map(([k, v]) => [k, v.replace(/\\/g, "/")]), | ||
| ), | ||
| }; | ||
|
|
||
| // ─── Step 0: Generate ripgrep base64 asset ─────────────────────────────────── | ||
| // Bun's bundler does not support ?url imports or arbitrary file embedding | ||
| // for non-.node files. The only reliable way to embed a binary into the | ||
| // compiled exe is to base64-encode it and store it as a JS string constant. | ||
| // At runtime, we decode to a temp file and execute. | ||
| async function generateRipgrepAsset() { | ||
| const rgCache = join(repoRoot, | ||
| `node_modules/.bun/@anthropic-ai+claude-agent-sdk@0.2.87+3c5d820c62823f0b/node_modules/@anthropic-ai/claude-agent-sdk/vendor/ripgrep`); | ||
|
|
||
| const ripgrepBinaries: Record<string, string> = {} | ||
|
|
||
| // Map platform+arch to filename | ||
| const allPlatforms: Array<{ key: string; subdir: string; file: string }> = [ | ||
| { key: 'windows_x64', subdir: 'x64-win32', file: 'rg.exe' }, | ||
| { key: 'darwin_x64', subdir: 'x64-darwin', file: 'rg' }, | ||
| { key: 'darwin_arm64', subdir: 'arm64-darwin', file: 'rg' }, | ||
| { key: 'linux_x64', subdir: 'x64-linux', file: 'rg' }, | ||
| { key: 'linux_arm64', subdir: 'arm64-linux', file: 'rg' }, | ||
| ]; | ||
|
|
||
| // Only embed the current platform's binary to minimize exe size. | ||
| // The other platforms are available in the SDK for dev-mode fallback. | ||
| const currentPlatformKey = (() => { | ||
| if (process.platform === 'win32') return 'windows_x64' | ||
| if (process.platform === 'darwin') return process.arch === 'arm64' ? 'darwin_arm64' : 'darwin_x64' | ||
| return process.arch === 'arm64' ? 'linux_arm64' : 'linux_x64' | ||
| })() | ||
|
|
||
| for (const { key, subdir, file } of allPlatforms) { | ||
| if (key !== currentPlatformKey) continue // Skip other platforms | ||
| const binPath = join(rgCache, subdir, file); | ||
| try { | ||
| const data = await readFile(binPath); | ||
| ripgrepBinaries[key] = data.toString('base64'); | ||
| console.log(`Encoded ${key}: ${data.length} bytes -> ${Math.round(data.length * 1.37)} chars`); | ||
| } catch (e) { | ||
| console.warn(`Warning: could not read ${binPath}: ${e}`); | ||
| } | ||
|
Comment on lines
+83
to
+115
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "Declared SDK dependency:"
node -e 'const p=require("./package.json"); console.log((p.dependencies||{})["@anthropic-ai/claude-agent-sdk"] || (p.devDependencies||{})["@anthropic-ai/claude-agent-sdk"])'
echo
echo "Hard-coded SDK cache references:"
rg -n -C2 '@anthropic-ai\+claude-agent-sdk@0\.2\.87|claude-agent-sdk@0\.2\.87|vendor/ripgrep'
echo
echo "Installed ripgrep vendor directories, if present:"
fd -p 'vendor/ripgrep' node_modules --max-depth 8 2>/dev/null || trueRepository: claude-code-best/claude-code Length of output: 2499 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== Full generateRipgrepAsset function in compile.ts ==="
sed -n '83,150p' compile.ts
echo
echo "=== How ripgrepBinaries is used/written ==="
rg -n -A5 "ripgrepBinaries" compile.ts | head -40
echo
echo "=== Check if there are dynamic SDK resolution patterns elsewhere ==="
rg -n "require.resolve|import.meta.resolve|resolve.*sdk" --type ts --type tsx -i | head -20Repository: claude-code-best/claude-code Length of output: 4337 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== Look for require.resolve patterns in TypeScript files ==="
rg -n "require\.resolve|resolveFrom|createRequire|import\.meta" --type-add 'ts:*.ts' --type-add 'tsx:*.tsx' | head -20
echo
echo "=== Check bun's resolve capabilities ==="
rg -n "bun:.*resolve|Bun\.resolveSync|Bun\.resolve" | head -10
echo
echo "=== Look at package.json to understand dependency management ==="
head -50 package.json
echo
echo "=== Check how other paths reference node_modules packages ==="
rg -n "node_modules.*@anthropic" --type-add 'ts:*.ts' --type-add 'tsx:*.tsx' -C1 | head -30Repository: claude-code-best/claude-code Length of output: 2238 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== Look for resolve patterns with createRequire ==="
rg -n -A5 "createRequire" compile.ts src/utils/ | head -40
echo
echo "=== Check how bun handles module resolution ==="
cat package.json | grep -A2 -B2 "anthropic"
echo
echo "=== Try to find if there's a way to dynamically resolve the package ==="
node -e "
const { createRequire } = require('module');
const req = createRequire(import.meta.url);
try {
const pkgPath = req.resolve('@anthropic-ai/claude-agent-sdk/package.json');
console.log('Resolved package:', pkgPath);
} catch(e) {
console.log('Could not resolve:', e.message);
}
"
echo
echo "=== Check compile.ts for any dynamic resolution at all ==="
head -30 compile.tsRepository: claude-code-best/claude-code Length of output: 124 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== Full compile.ts head to see imports ==="
head -40 compile.ts
echo
echo "=== Check if file paths are computed anywhere else ==="
grep -r "resolveSync\|resolve(" compile.ts src/ 2>/dev/null || echo "No resolve patterns found"
echo
echo "=== Look at the actual imports in compile.ts ==="
rg -n "^import\|^from" compile.ts | head -20
echo
echo "=== Check how the SDK is actually installed - bun.lockb or package-lock ==="
ls -la | grep -E "lock|package"
echo
echo "=== Look for any bun-specific resolution ==="
rg -n "Bun\." compile.ts | head -10Repository: claude-code-best/claude-code Length of output: 37574 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== Check imports in compile.ts ==="
head -20 compile.ts
echo
echo "=== Check if createRequire is used anywhere in compile context ==="
rg -n "createRequire" compile.ts
echo
echo "=== Look at how ripgrep.ts (runtime) resolves the path dynamically ==="
cat -n src/utils/ripgrep.ts | head -30
echo
echo "=== Check the actual generated ripgrepAssetBase64.ts file to see if it exists ==="
ls -la src/utils/ripgrepAssetBase64.ts 2>/dev/null || echo "File does not exist yet"
echo
echo "=== Can we use require.resolve or import.meta.resolve in compile.ts? ==="
rg -n "require\.resolve|import\.meta\.resolve" compile.ts src/Repository: claude-code-best/claude-code Length of output: 980 🏁 Script executed: #!/bin/bash
set -euoripefail
echo "=== Full ripgrep.ts to see dynamic resolution pattern ==="
cat -n src/utils/ripgrep.ts
echo
echo "=== Check if bun provides module resolution in compile scripts ==="
# Look for examples of how dependencies are resolved at build time
rg -n "package.json\|require.*resolve\|fileURLToPath" scripts/ --type ts -A2 | head -40
echo
echo "=== Verify the SDK path mismatch more clearly ==="
echo "Declared in package.json:"
jq '.dependencies["@anthropic-ai/claude-agent-sdk"]' package.json
echo
echo "Hard-coded in compile.ts:"
grep -n "@anthropic-ai" compile.ts | head -3Repository: claude-code-best/claude-code Length of output: 746 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== Full ripgrep.ts to see dynamic resolution pattern ==="
cat -n src/utils/ripgrep.ts
echo
echo "=== Check if bun provides module resolution in compile scripts ==="
# Look for examples of how dependencies are resolved at build time
rg -n "package.json|require.*resolve|fileURLToPath" scripts/ --type ts -A2 | head -40
echo
echo "=== Verify the SDK path mismatch more clearly ==="
echo "Declared in package.json:"
jq '.dependencies["@anthropic-ai/claude-agent-sdk"]' package.json
echo
echo "Hard-coded in compile.ts:"
grep -n "@anthropic-ai" compile.ts | head -3Repository: claude-code-best/claude-code Length of output: 27419 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== Check actual package.json dependencies section ==="
cat package.json | grep -A50 '"dependencies"'
echo
echo "=== Check src/utils/ripgrepAsset.ts - where getRipgrepBinaryPath comes from ==="
cat -n src/utils/ripgrepAsset.ts
echo
echo "=== Confirm: does generateRipgrepAsset fail compile if ripgrep missing? ==="
grep -n "generateRipgrepAsset" compile.ts | head -5Repository: claude-code-best/claude-code Length of output: 8190 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== Final verification: Check how to resolve SDK location dynamically ==="
echo "Checking if require.resolve or similar could work in compile.ts context:"
node -e "
try {
const path = require.resolve('@anthropic-ai/claude-agent-sdk');
console.log('Resolved SDK path:', path);
} catch (e) {
console.log('Cannot resolve:', e.message);
}
"
echo
echo "=== Check if bun has native resolve capabilities ==="
grep -n "Bun\|bun:" compile.ts | head -5Repository: claude-code-best/claude-code Length of output: 717 Fail the build when the embedded ripgrep asset is missing, and resolve the SDK location dynamically. When ripgrep cannot be read (line 114), the code warns but continues, writing an empty Use 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| // Generate TypeScript asset file | ||
| const assetFile = join(repoRoot, "src", "utils", "ripgrepAssetBase64.ts"); | ||
| const content = `/** | ||
| * AUTO-GENERATED by compile.ts — do not edit manually. | ||
| * Ripgrep binaries encoded as base64 strings. | ||
| * Decoded at runtime to temp files for execution. | ||
| */ | ||
| export const RIPGREP_BINARIES: Record<string, string> = ${JSON.stringify(ripgrepBinaries, null, 2)}; | ||
| `; | ||
| await writeFile(assetFile, content); | ||
| console.log(`Generated ${assetFile}`); | ||
| } | ||
|
|
||
| // ─── Step 1: Patch SDK ripgrep path ─────────────────────────────────────────── | ||
| // The SDK's cli.js computes dy_ from import.meta.url which points to B:\~BUN\root\... | ||
| // in --compile mode. Patch it to use path.dirname(process.execPath) instead. | ||
| async function patchRipgrepPaths() { | ||
| // --- Patch bun cache SDK cli.js --- | ||
| const sdkCachePath = join(repoRoot, | ||
| "node_modules/.bun/@anthropic-ai+claude-agent-sdk@0.2.87+3c5d820c62823f0b/node_modules/@anthropic-ai/claude-agent-sdk/cli.js"); | ||
| const sdkContent = await readFile(sdkCachePath, "utf-8"); | ||
| const patchedSdk = sdkContent | ||
| .replace( | ||
| /import\{fileURLToPath as Uy_\}from"url";/, | ||
| ";", | ||
| ) | ||
| .replace( | ||
| /dy_=Uy_\(import\.meta\.url\),dy_=Z16\.join\(dy_,"\.\/"\)/, | ||
| "dy_=Z16.dirname(process.execPath)", | ||
| ); | ||
| if (patchedSdk === sdkContent) { | ||
| console.warn("Warning: SDK patch did not match"); | ||
| } else { | ||
| await writeFile(sdkCachePath, patchedSdk); | ||
| console.log("Patched SDK cli.js (bun cache)"); | ||
| } | ||
|
Comment on lines
+134
to
+153
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "SDK patch targets/references:"
rg -n -C3 'patchRipgrepPaths|SDK patch did not match|dy_=|@anthropic-ai\+claude-agent-sdk@0\.2\.87'
echo
echo "Installed SDK cli.js files, if present:"
fd -p 'claude-agent-sdk/cli.js' node_modules --max-depth 8 2>/dev/null || trueRepository: claude-code-best/claude-code Length of output: 3349 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== Checking SDK version pinning ==="
echo "From package.json:"
rg '"@anthropic-ai/claude-agent-sdk"' package.json -A2 -B2
echo
echo "=== Checking for hard-coded paths in compile.ts ==="
rg '@anthropic-ai.*0\.2\.87' compile.ts -n
echo
echo "=== Checking if patchRipgrepPaths handles errors ==="
sed -n '134,153p' compile.ts | cat -n
echo
echo "=== Looking for error handling patterns in similar functions ==="
rg 'throw new Error|process\.exit|throw Error' compile.ts -nRepository: claude-code-best/claude-code Length of output: 1768 Make the SDK patch deterministic and fail-fast; remove hard-coded version strings. The code hard-codes SDK version Throw an error on patch failure instead of silently logging a warning. Dynamically resolve the SDK path from 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| // ─── Step 2: Run the compile ─────────────────────────────────────────────────── | ||
| async function run() { | ||
| await generateRipgrepAsset(); | ||
| await patchRipgrepPaths(); | ||
|
|
||
| console.log("\nCompiling standalone executable with native modules..."); | ||
| console.log(`Outfile: ${outfile}`); | ||
| console.log(`Defines: ${Object.keys(defines).join(", ")}`); | ||
| console.log(`Native modules:`); | ||
| for (const [k, v] of Object.entries(nativeNodePaths)) { | ||
| console.log(` ${k}=${v}`); | ||
| } | ||
|
|
||
| // Use Bun.spawn with CLI because Bun.build({ outfile, compile: true }) | ||
| // does not reliably place the output file on Windows. | ||
| const result = Bun.spawnSync( | ||
| [ | ||
| bunExe, | ||
| "build", | ||
| "--compile", | ||
| "--outfile=" + outfile, | ||
| ...defineArgsWithBundled, | ||
| ...featureArgs, | ||
| "src/entrypoints/cli.tsx", | ||
| ], | ||
| { | ||
| stdio: ["inherit", "inherit", "inherit"], | ||
| env: compileEnv, | ||
| }, | ||
| ); | ||
|
|
||
| if (result.exitCode !== 0) { | ||
| console.error("Compile failed with exit code:", result.exitCode); | ||
| exit(1); | ||
| } | ||
|
|
||
| console.log(`\nCompiled standalone executable: ${outfile}`); | ||
| if (features.length > 0) { | ||
| console.log(`Features enabled: ${features.join(", ")}`); | ||
| } | ||
| } | ||
|
|
||
| run(); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,9 +11,14 @@ export function isRunningWithBun(): boolean { | |
|
|
||
| /** | ||
| * Detects if running as a Bun-compiled standalone executable. | ||
| * This checks for embedded files which are present in compiled binaries. | ||
| * | ||
| * Primary check: Bun.embeddedFiles (present in compiled binaries). | ||
| * Fallback: BUNDLED_MODE compile-time constant injected by compile.ts. | ||
| */ | ||
| // BUNDLED_MODE is injected at compile time by compile.ts --define flag. | ||
| declare const BUNDLED_MODE: string | undefined | ||
| export function isInBundledMode(): boolean { | ||
| if (typeof BUNDLED_MODE !== 'undefined') return true | ||
| return ( | ||
|
Comment on lines
+14
to
22
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Align the bundled-mode check with the documented precedence. The comment says - * Primary check: Bun.embeddedFiles (present in compiled binaries).
- * Fallback: BUNDLED_MODE compile-time constant injected by compile.ts.
+ * Primary check: BUNDLED_MODE compile-time constant injected by compile.ts.
+ * Fallback: Bun.embeddedFiles (present in compiled binaries).
...
- if (typeof BUNDLED_MODE !== 'undefined') return true
+ if (BUNDLED_MODE === 'true') return true🤖 Prompt for AI Agents |
||
| typeof Bun !== 'undefined' && | ||
| Array.isArray(Bun.embeddedFiles) && | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ import * as path from 'path' | |
| import { logEvent } from 'src/services/analytics/index.js' | ||
| import { fileURLToPath } from 'url' | ||
| import { isInBundledMode } from './bundledMode.js' | ||
| import { getRipgrepBinaryPath } from './ripgrepAsset.js' | ||
| import { logForDebugging } from './debug.js' | ||
| import { isEnvDefinedFalsy } from './envUtils.js' | ||
| import { execFileNoThrow } from './execFileNoThrow.js' | ||
|
|
@@ -44,15 +45,11 @@ const getRipgrepConfig = memoize((): RipgrepConfig => { | |
| } | ||
| } | ||
|
|
||
| // In bundled (native) mode, ripgrep is statically compiled into bun-internal | ||
| // and dispatches based on argv[0]. We spawn ourselves with argv0='rg'. | ||
| // In bundled mode (compiled exe), ripgrep is embedded via base64. | ||
| // Extract to temp and execute from there. | ||
| if (isInBundledMode()) { | ||
| return { | ||
| mode: 'embedded', | ||
| command: process.execPath, | ||
| args: ['--no-config'], | ||
| argv0: 'rg', | ||
| } | ||
| const rgPath = getRipgrepBinaryPath() | ||
| return { mode: 'builtin', command: rgPath, args: [] } | ||
|
Comment on lines
+48
to
+52
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Keep compiled ripgrep distinct from vendored ripgrep. Returning 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| const rgRoot = path.resolve(__dirname, 'vendor', 'ripgrep') | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,122 @@ | ||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||
| * Gets the ripgrep binary path for the current platform/arch. | ||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||
| * In compiled mode: decodes base64 from ripgrepAssetBase64.ts, writes to temp, | ||||||||||||||||||||||||||||||||
| * and caches on disk so subsequent starts skip the decode. | ||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||
| * In dev mode: falls back to SDK's bundled ripgrep path. | ||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||
| * BUNDLED_MODE is a compile-time constant injected by compile.ts --define flag. | ||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||
| import { writeFileSync, readFileSync } from 'fs' | ||||||||||||||||||||||||||||||||
| import { mkdirSync } from 'fs' | ||||||||||||||||||||||||||||||||
| import { tmpdir } from 'os' | ||||||||||||||||||||||||||||||||
| import { join } from 'path' | ||||||||||||||||||||||||||||||||
| import { getPlatform } from './platform.js' | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // In-memory cache: platform+arch -> absolute path to extracted temp file | ||||||||||||||||||||||||||||||||
| const extractedPaths: Record<string, string> = {} | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // Global base64 data — loaded once on first access | ||||||||||||||||||||||||||||||||
| let globalBase64: Record<string, string> | null = null | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // SDK's bundled ripgrep path (used as fallback in dev mode) | ||||||||||||||||||||||||||||||||
| function getSdkRipgrepPath(): string { | ||||||||||||||||||||||||||||||||
| const p = getPlatform() | ||||||||||||||||||||||||||||||||
| const arch = process.arch | ||||||||||||||||||||||||||||||||
| if (p === 'windows') return join(process.cwd(), 'node_modules/.bun/@anthropic-ai+claude-agent-sdk@0.2.87+3c5d820c62823f0b/node_modules/@anthropic-ai/claude-agent-sdk/vendor/ripgrep/x64-win32/rg.exe') | ||||||||||||||||||||||||||||||||
| if (p === 'macos') return join(process.cwd(), 'node_modules/.bun/@anthropic-ai+claude-agent-sdk@0.2.87+3c5d820c62823f0b/node_modules/@anthropic-ai/claude-agent-sdk/vendor/ripgrep', arch === 'arm64' ? 'arm64-darwin/rg' : 'x64-darwin/rg') | ||||||||||||||||||||||||||||||||
| return join(process.cwd(), 'node_modules/.bun/@anthropic-ai+claude-agent-sdk@0.2.87+3c5d820c62823f0b/node_modules/@anthropic-ai/claude-agent-sdk/vendor/ripgrep', arch === 'arm64' ? 'arm64-linux/rg' : 'x64-linux/rg') | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
Comment on lines
+24
to
+30
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "Declared SDK dependency:"
node -e 'const p=require("./package.json"); console.log((p.dependencies||{})["@anthropic-ai/claude-agent-sdk"] || (p.devDependencies||{})["@anthropic-ai/claude-agent-sdk"])'
echo
echo "Hard-coded SDK cache references:"
rg -n -C2 '@anthropic-ai\+claude-agent-sdk@0\.2\.87|claude-agent-sdk@0\.2\.87|claude-agent-sdk/vendor/ripgrep'
echo
echo "Installed Bun cache SDK dirs, if present:"
fd -p '@anthropic-ai+claude-agent-sdk' node_modules/.bun --max-depth 2 2>/dev/null || trueRepository: claude-code-best/claude-code Length of output: 1841 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== Content of ripgrepAsset.ts at specified lines ==="
sed -n '20,35p' src/utils/ripgrepAsset.ts | cat -n
echo
echo "=== Content at lines 85-95 (also applies to comment) ==="
sed -n '85,95p' src/utils/ripgrepAsset.ts | cat -n
echo
echo "=== Check if require.resolve or similar is used elsewhere ==="
rg -n 'require\.resolve|import\.meta\.resolve|resolve\(.*@anthropic-ai' src/ --type ts --type tsx | head -20
echo
echo "=== Check what functions are exported from ripgrepAsset.ts ==="
rg -n 'export' src/utils/ripgrepAsset.ts
echo
echo "=== Full ripgrepAsset.ts file size ==="
wc -l src/utils/ripgrepAsset.tsRepository: claude-code-best/claude-code Length of output: 1823 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== Check if require.resolve or similar is used elsewhere ==="
rg -n 'require\.resolve|import\.meta\.resolve' src/ | head -20
echo
echo "=== Check how SDK is resolved in other places ==="
rg -n '@anthropic-ai/claude-agent-sdk' src/ | grep -v 'node_modules'
echo
echo "=== Full ripgrepAsset.ts to understand complete context ==="
wc -l src/utils/ripgrepAsset.ts
cat -n src/utils/ripgrepAsset.tsRepository: claude-code-best/claude-code Length of output: 133 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "=== Full ripgrepAsset.ts file ==="
cat -n src/utils/ripgrepAsset.ts
echo
echo "=== Check for process.cwd() usage in the repo ==="
rg 'process\.cwd\(\)' src/ -c
echo
echo "=== Check how SDK packages are typically resolved ==="
rg '@anthropic-ai' src/ -lRepository: claude-code-best/claude-code Length of output: 10486 Use dynamic module resolution instead of hard-coded SDK path. The function hard-codes Resolve the vendor directory dynamically (e.g., via This affects the dev-mode fallback at lines 88–91 as well. 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| function getPlatformKey(): string { | ||||||||||||||||||||||||||||||||
| const platform = getPlatform() | ||||||||||||||||||||||||||||||||
| const arch = process.arch | ||||||||||||||||||||||||||||||||
| if (platform === 'windows') return 'windows_x64' | ||||||||||||||||||||||||||||||||
| if (platform === 'macos') return arch === 'arm64' ? 'darwin_arm64' : 'darwin_x64' | ||||||||||||||||||||||||||||||||
| return arch === 'arm64' ? 'linux_arm64' : 'linux_x64' | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // BUNDLED_MODE is injected at compile time by compile.ts --define flag. | ||||||||||||||||||||||||||||||||
| // In dev mode, this variable is undefined. | ||||||||||||||||||||||||||||||||
| declare const BUNDLED_MODE: string | undefined | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||
| * Load base64 data asynchronously (first call only). | ||||||||||||||||||||||||||||||||
| * Subsequent calls use the cached global. | ||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||
| async function ensureBase64Loaded(): Promise<Record<string, string>> { | ||||||||||||||||||||||||||||||||
| if (globalBase64 !== null) return globalBase64 | ||||||||||||||||||||||||||||||||
| // Dynamic import so the 6.9MB base64 string isn't loaded in dev mode | ||||||||||||||||||||||||||||||||
| const mod = await import('./ripgrepAssetBase64.js') | ||||||||||||||||||||||||||||||||
| globalBase64 = mod.RIPGREP_BINARIES ?? {} | ||||||||||||||||||||||||||||||||
| return globalBase64 | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||
| * Get the ripgrep binary path for the current platform/arch. | ||||||||||||||||||||||||||||||||
| * In compiled mode: decodes base64, extracts to temp, caches by version fingerprint. | ||||||||||||||||||||||||||||||||
| * In dev mode: returns SDK path directly. | ||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||
| export function getRipgrepBinaryPath(): string { | ||||||||||||||||||||||||||||||||
| const key = getPlatformKey() | ||||||||||||||||||||||||||||||||
| if (extractedPaths[key]) return extractedPaths[key] | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| const tmpDir = join(tmpdir(), 'claude-code-ripgrep') | ||||||||||||||||||||||||||||||||
| const filename = key === 'windows_x64' ? 'rg.exe' : 'rg' | ||||||||||||||||||||||||||||||||
| const filePath = join(tmpDir, filename) | ||||||||||||||||||||||||||||||||
| const versionPath = join(tmpDir, `${key}.version`) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // Dev mode: use SDK path directly | ||||||||||||||||||||||||||||||||
| if (typeof BUNDLED_MODE === 'undefined') { | ||||||||||||||||||||||||||||||||
| const sdkPath = getSdkRipgrepPath() | ||||||||||||||||||||||||||||||||
| extractedPaths[key] = sdkPath | ||||||||||||||||||||||||||||||||
| return sdkPath | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
Comment on lines
+70
to
+75
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use the same bundled-mode detection as callers.
+import { isInBundledMode } from 'src/utils/bundledMode.js'
...
- if (typeof BUNDLED_MODE === 'undefined') {
+ if (!isInBundledMode()) {📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // Compiled mode: must use base64 decode (synchronous path — loaded eagerly from embedded module) | ||||||||||||||||||||||||||||||||
| // In the compiled exe, require() resolves to the embedded ripgrepAssetBase64.js | ||||||||||||||||||||||||||||||||
| let base64Data: string | undefined | ||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||
| // eslint-disable-next-line @typescript-eslint/no-var-requires | ||||||||||||||||||||||||||||||||
| const RIPGREP_BINARIES: Record<string, string> = require('./ripgrepAssetBase64.js').RIPGREP_BINARIES | ||||||||||||||||||||||||||||||||
| base64Data = RIPGREP_BINARIES[key] | ||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||
| // require failed — fall back to SDK path | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if (!base64Data) { | ||||||||||||||||||||||||||||||||
| const sdkPath = getSdkRipgrepPath() | ||||||||||||||||||||||||||||||||
| extractedPaths[key] = sdkPath | ||||||||||||||||||||||||||||||||
| return sdkPath | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| const versionTag = `b64:${base64Data.length}:${base64Data.slice(0, 16)}:${base64Data.slice(-16)}` | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // Fast cache check: read only the version tag (~50 bytes) | ||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||
| const storedTag = readFileSync(versionPath, 'utf8') | ||||||||||||||||||||||||||||||||
| if (storedTag === versionTag && readFileSync(filePath)) { | ||||||||||||||||||||||||||||||||
| extractedPaths[key] = filePath | ||||||||||||||||||||||||||||||||
| return filePath | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||
| // Cache miss or stale | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // Decode and extract | ||||||||||||||||||||||||||||||||
| mkdirSync(tmpDir, { recursive: true }) | ||||||||||||||||||||||||||||||||
| const buffer = Buffer.from(base64Data, 'base64') | ||||||||||||||||||||||||||||||||
| writeFileSync(filePath, buffer, { mode: 0o755 }) | ||||||||||||||||||||||||||||||||
| writeFileSync(versionPath, versionTag, 'utf8') | ||||||||||||||||||||||||||||||||
|
Comment on lines
+65
to
+111
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Harden the temp executable cache before trusting it. The cache path is predictable and shared under 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||
| extractedPaths[key] = filePath | ||||||||||||||||||||||||||||||||
| return filePath | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||
| * Async version — preloads base64 data before extracting. | ||||||||||||||||||||||||||||||||
| * Call this early (e.g., during startup) to avoid decode delay on first grep. | ||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||
| export async function preloadRipgrepBinary(): Promise<void> { | ||||||||||||||||||||||||||||||||
| getRipgrepBinaryPath() | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
Large diffs are not rendered by default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only enable feature flags when their value is
1.The current code enables
FEATURE_FOO=0,FEATURE_FOO=false, or an empty variable. Filter by the required enablement value before passing--feature.As per coding guidelines, “Feature flags in build must use environment variable format
FEATURE_<FLAG_NAME>=1for enablement”.📝 Committable suggestion
🤖 Prompt for AI Agents