feat: bundled 模式改用 base64 嵌入 ripgrep 二进制替代 argv0 派发#113
feat: bundled 模式改用 base64 嵌入 ripgrep 二进制替代 argv0 派发#113realorange1994 wants to merge 7 commits intoclaude-code-best:mainfrom
Conversation
Bun's --compile mode does not support ?url imports or new URL() for embedding non-.node binary files. This change uses base64 encoding to bundle ripgrep binaries into the exe. - compile.ts generates ripgrepAssetBase64.ts at build time, encoding all 5 platform binaries (windows_x64, darwin_x64, darwin_arm64, linux_x64, linux_arm64) as base64 strings - ripgrepAsset.ts decodes base64 to a temp file at runtime (compiled mode) or falls back to SDK's bundled ripgrep path (dev mode) - ripgrep.ts refactored to async, with ripgrepCommand() now returning a Promise - Added compile script to package.json
- compile.ts: inject BUNDLED_MODE="true" define so bundledMode.ts
detects compiled exe correctly (Bun.embeddedFiles is always empty)
- bundledMode.ts: check BUNDLED_MODE compile-time flag as primary
detection, fall back to Bun.embeddedFiles check
- ripgrepAsset.ts: switch from Bun.file() approach (which reads from
filesystem, not embedded data) to require('./ripgrepAssetBase64.js')
which loads the embedded base64 data in the compiled exe. Dev mode
falls back to SDK path. Includes disk caching via version fingerprint.
- ripgrep.ts: in bundled mode, call getRipgrepBinaryPath() to get the
extracted temp path instead of using process.execPath with argv0
(which requires native ripgrep embedding we don't have access to)
Bun's --compile mode does not support ?url imports or new URL() for embedding non-.node binary files. This change uses base64 encoding to bundle ripgrep binaries into the exe. - compile.ts generates ripgrepAssetBase64.ts at build time, encoding all 5 platform binaries (windows_x64, darwin_x64, darwin_arm64, linux_x64, linux_arm64) as base64 strings - ripgrepAsset.ts decodes base64 to a temp file at runtime (compiled mode) or falls back to SDK's bundled ripgrep path (dev mode) - ripgrep.ts refactored to async, with ripgrepCommand() now returning a Promise - Added compile script to package.json
- compile.ts: inject BUNDLED_MODE="true" define so bundledMode.ts
detects compiled exe correctly (Bun.embeddedFiles is always empty)
- bundledMode.ts: check BUNDLED_MODE compile-time flag as primary
detection, fall back to Bun.embeddedFiles check
- ripgrepAsset.ts: switch from Bun.file() approach (which reads from
filesystem, not embedded data) to require('./ripgrepAssetBase64.js')
which loads the embedded base64 data in the compiled exe. Dev mode
falls back to SDK path. Includes disk caching via version fingerprint.
- ripgrep.ts: in bundled mode, call getRipgrepBinaryPath() to get the
extracted temp path instead of using process.execPath with argv0
(which requires native ripgrep embedding we don't have access to)
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughNew build tooling adds a Bun-based compile script that embeds platform-specific native modules and ripgrep binaries (as base64), patches an SDK runtime path for compiled mode, and introduces runtime utilities to resolve and extract embedded ripgrep binaries with caching and fallbacks. Changes
Sequence Diagram(s)sequenceDiagram
participant Dev as Developer / CLI
participant Compile as compile.ts
participant FS as File System
participant SDK as Claude Agent SDK (cli.js)
participant Bun as Bun (build)
Note over Compile,FS: compile.ts run via `bun run compile.ts`
Dev->>Compile: invoke compile
Compile->>FS: read SDK ripgrep binary for platform/arch
FS-->>Compile: ripgrep binary bytes
Compile->>FS: write `src/utils/ripgrepAssetBase64.ts` (base64 map)
Compile->>FS: patch SDK `cli.js` in node_modules
Compile->>Bun: spawn `bun build --compile` with defines, features, env
Bun->>FS: read embedded native modules and asset map
Bun-->>Compile: build success / failure
alt failure
Compile->>Dev: exit with code 1
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/utils/ripgrep.ts (1)
25-30: 🛠️ Refactor suggestion | 🟠 MajorRemove dead
'embedded'mode from type union—it's never returned bygetRipgrepConfig().The
RipgrepConfigtype includes'embedded'as a mode, but all code paths ingetRipgrepConfig()return only'system'or'builtin'. This creates dead code and confuses maintainers.Proposed fixes needed in three locations
- ripgrep.ts line 26 (RipgrepConfig type):
- mode: 'system' | 'builtin' | 'embedded' + mode: 'system' | 'builtin'
- ripgrep.ts line 533 (getRipgrepStatus return type):
- mode: 'system' | 'builtin' | 'embedded' + mode: 'system' | 'builtin'
- doctorDiagnostic.ts line 68 (DiagnosticInfo.ripgrepStatus type):
- mode: 'system' | 'builtin' | 'embedded' + mode: 'system' | 'builtin'Additionally, Doctor.tsx line 315 contains a dead code check (
mode === "embedded") that should also be cleaned up once the type is narrowed.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/utils/ripgrep.ts` around lines 25 - 30, Remove the dead 'embedded' variant everywhere: update the RipgrepConfig type (remove 'embedded' from mode union), change the return type of getRipgrepStatus to only allow 'system' | 'builtin', and adjust DiagnosticInfo.ripgrepStatus to the narrowed union; then delete the dead branch in Doctor.tsx that checks for mode === "embedded". Ensure all references to the embedded mode (type literals, conditional checks) are removed or updated so the code and types only reflect 'system' and 'builtin' and re-run type checks.
🧹 Nitpick comments (1)
src/utils/ripgrepAsset.ts (1)
44-54: Dead code:ensureBase64Loaded()is never called.The async function
ensureBase64Loaded()and theglobalBase64variable are defined but never used. The synchronous path ingetRipgrepBinaryPath()usesrequire()directly instead. Similarly,preloadRipgrepBinary()at lines 120-122 claims to be async but just calls the sync function.♻️ Proposed fix to remove dead code or implement proper async preloading
Either remove the unused async machinery:
-// Global base64 data — loaded once on first access -let globalBase64: Record<string, string> | null = null - ... -/** - * 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 -}Or implement proper async preloading:
export async function preloadRipgrepBinary(): Promise<void> { - getRipgrepBinaryPath() + await ensureBase64Loaded() + getRipgrepBinaryPath() }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/utils/ripgrepAsset.ts` around lines 44 - 54, The async helper ensureBase64Loaded and the globalBase64 cache are dead code because getRipgrepBinaryPath uses a synchronous require and preloadRipgrepBinary only calls the sync path; either remove ensureBase64Loaded and globalBase64 and make preloadRipgrepBinary a simple sync noop (or delete it), or refactor getRipgrepBinaryPath to use ensureBase64Loaded (await it) and make preloadRipgrepBinary call await ensureBase64Loaded so the module can be preloaded asynchronously; update references to ensureBase64Loaded, globalBase64, getRipgrepBinaryPath, and preloadRipgrepBinary accordingly so there are no unused symbols left.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@compile.ts`:
- Around line 83-86: The hardcoded vendor path in generateRipgrepAsset (and the
other occurrences) will break on upgrades; replace the literal join(...) that
embeds `@anthropic-ai+claude-agent-sdk@0.2.87+...` with dynamic module
resolution (e.g., use require.resolve or import.meta.resolve to locate the
package root and then append `/vendor/ripgrep`), so the code discovers the
installed `@anthropic-ai/claude-agent-sdk` location at runtime; apply the same
change to the other occurrences (the generateRipgrepAsset function and the
references in src/utils/ripgrepAsset.ts) so they all derive the SDK path
dynamically instead of using a pinned version string.
- Around line 139-153: The regex-based patching of the minified SDK (operating
on sdkContent to produce patchedSdk using patterns matching Uy_ and dy_=Uy_) is
brittle and currently only emits a console.warn if no replacement occurred;
change this to fail the build by throwing or exiting with a non-zero status when
patchedSdk === sdkContent so the CI stops on mismatch, and add a clear error
message including sdkCachePath and the original fragment names (Uy_, dy_=Uy_) to
aid debugging; additionally, replace this fragile approach in the long term by
switching the patching logic from regex to an AST-based transform (or at minimum
add an SDK version check and a documented targeted version comment near the
patch code and function writeFile usage) so future minifier/name changes are
detected and handled safely.
- Around line 50-68: nativeNodePaths contains hardcoded arm64-darwin values
causing cross-platform build failures; update the path construction for
COMPUTER_USE_INPUT_NODE_PATH, COMPUTER_USE_SWIFT_NODE_PATH, MODIFIERS_NODE_PATH,
and URL_HANDLER_NODE_PATH inside the nativeNodePaths object to choose
architecture/platform dynamically (use process.arch and process.platform like
the AUDIO_CAPTURE_NODE_PATH/IMAGE_PROCESSOR_NODE_PATH entries) so the
join(repoRoot, ...) paths point to the correct prebuild/vendor subfolders for
the current platform; ensure any macOS-only modules still guard runtime loads
with process.platform checks and that the join and repoRoot symbols remain used
for path assembly.
In `@scripts/download-ripgrep.ts`:
- Line 21: Replace the third‑party proxy BASE_URL (`BASE_URL =
...gh-proxy.com...`) to use the official GitHub release URL constructed with
RG_VERSION, and add SHA256 checksum verification after the file is downloaded:
create an EXPECTED_CHECKSUMS mapping keyed by the release asset filename,
compute the downloaded file's SHA256, compare against EXPECTED_CHECKSUMS entry,
and abort (throw/log and exit) if the checksum does not match before any
extraction or execution; update the download/verify flow where the asset is
fetched to perform this check.
In `@src/utils/bundledMode.ts`:
- Around line 12-21: The docstring for isInBundledMode is inconsistent with the
implementation: the code checks the compile-time BUNDLED_MODE first and
Bun.embeddedFiles second, so update the comment to state "Primary check:
BUNDLED_MODE (compile-time constant) and Fallback: Bun.embeddedFiles" and adjust
any wording to reference the BUNDLED_MODE declaration and the isInBundledMode
function so the comment accurately reflects the code flow.
In `@src/utils/ripgrep.ts`:
- Around line 48-52: The current bundled extraction branch in isInBundledMode()
returns mode: 'builtin' (see getRipgrepBinaryPath() usage), which now maps to
"vendor" in the Doctor UI and misrepresents compiled executables; change the
returned mode value from 'builtin' to a distinct value that preserves the
previous UI label (e.g., 'embedded' or 'bundled') or add a new mode like
'bundled' so Doctor.tsx's mapping (which currently translates
'embedded'→'bundled' and 'builtin'→'vendor') can correctly display
compiled/embedded ripgrep; update the return in the bundled branch to use that
chosen mode and adjust any consumers expecting 'builtin' as needed.
In `@src/utils/ripgrepAsset.ts`:
- Around line 96-105: The cache fast-path returns filePath after calling
readFileSync(filePath) without verifying the file contents; change the check so
that after confirming storedTag === versionTag you read the file into a variable
(using readFileSync) and validate it is non-empty (size/length > 0) and ideally
stat the file (fs.statSync(filePath)) to ensure size > 0 and executable
permission before setting extractedPaths[key] = filePath and returning; on any
validation failure fall through to the extraction path and keep the existing
try/catch behavior.
- Around line 32-38: getPlatformKey currently always returns 'windows_x64' for
Windows and ignores process.arch, breaking Windows ARM64 support; update
getPlatformKey (which calls getPlatform and uses process.arch) to return
'windows_arm64' when getPlatform() === 'windows' and process.arch === 'arm64',
otherwise keep existing mappings for darwin and linux; after this change also
add 'windows_arm64' to the allPlatforms list in compile.ts and ensure the ARM64
ripgrep binary is sourced for packaging.
- Around line 107-113: The extracted ripgrep binary lacks execute permission
after writeFileSync, causing EACCES when running on Unix; after writing the file
at filePath (in the extraction routine in src/utils/ripgrepAsset.ts where
tmpDir, base64Data, writeFileSync, versionPath, versionTag, and extractedPaths
are used), set executable permissions (e.g. chmod 0o755) on filePath—use
fs.chmodSync or fs.promises.chmod (with a safe try/catch) so macOS/Linux can
execute the rg binary while leaving Windows behavior unchanged.
---
Outside diff comments:
In `@src/utils/ripgrep.ts`:
- Around line 25-30: Remove the dead 'embedded' variant everywhere: update the
RipgrepConfig type (remove 'embedded' from mode union), change the return type
of getRipgrepStatus to only allow 'system' | 'builtin', and adjust
DiagnosticInfo.ripgrepStatus to the narrowed union; then delete the dead branch
in Doctor.tsx that checks for mode === "embedded". Ensure all references to the
embedded mode (type literals, conditional checks) are removed or updated so the
code and types only reflect 'system' and 'builtin' and re-run type checks.
---
Nitpick comments:
In `@src/utils/ripgrepAsset.ts`:
- Around line 44-54: The async helper ensureBase64Loaded and the globalBase64
cache are dead code because getRipgrepBinaryPath uses a synchronous require and
preloadRipgrepBinary only calls the sync path; either remove ensureBase64Loaded
and globalBase64 and make preloadRipgrepBinary a simple sync noop (or delete
it), or refactor getRipgrepBinaryPath to use ensureBase64Loaded (await it) and
make preloadRipgrepBinary call await ensureBase64Loaded so the module can be
preloaded asynchronously; update references to ensureBase64Loaded, globalBase64,
getRipgrepBinaryPath, and preloadRipgrepBinary accordingly so there are no
unused symbols left.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 3dffce1e-6b62-4412-bbfe-30a65b8abcb6
📒 Files selected for processing (7)
compile.tspackage.jsonscripts/download-ripgrep.tssrc/utils/bundledMode.tssrc/utils/ripgrep.tssrc/utils/ripgrepAsset.tssrc/utils/ripgrepAssetBase64.ts
| 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`), | ||
| }; |
There was a problem hiding this comment.
Cross-platform build will fail: hardcoded arm64-darwin paths.
The native module paths for COMPUTER_USE_INPUT_NODE_PATH, COMPUTER_USE_SWIFT_NODE_PATH, MODIFIERS_NODE_PATH, and URL_HANDLER_NODE_PATH are hardcoded to arm64-darwin. Building on Windows or Linux will reference non-existent paths, causing Bun to fail when trying to embed these assets.
♻️ Proposed fix for platform-aware paths
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"),
+ ...(process.platform === 'darwin' ? {
+ COMPUTER_USE_INPUT_NODE_PATH: join(repoRoot,
+ `packages/@ant/computer-use-input/prebuilds/${process.arch}-darwin/computer-use-input.node`),
+ COMPUTER_USE_SWIFT_NODE_PATH: join(repoRoot,
+ `packages/@ant/computer-use-swift/prebuilds/${process.arch}-darwin/computer_use.node`),
+ } : {}),📝 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.
| 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`), | |
| }; | |
| 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. | |
| ...(process.platform === 'darwin' ? { | |
| COMPUTER_USE_INPUT_NODE_PATH: join(repoRoot, | |
| `packages/@ant/computer-use-input/prebuilds/${process.arch}-darwin/computer-use-input.node`), | |
| COMPUTER_USE_SWIFT_NODE_PATH: join(repoRoot, | |
| `packages/@ant/computer-use-swift/prebuilds/${process.arch}-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`), | |
| }; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@compile.ts` around lines 50 - 68, nativeNodePaths contains hardcoded
arm64-darwin values causing cross-platform build failures; update the path
construction for COMPUTER_USE_INPUT_NODE_PATH, COMPUTER_USE_SWIFT_NODE_PATH,
MODIFIERS_NODE_PATH, and URL_HANDLER_NODE_PATH inside the nativeNodePaths object
to choose architecture/platform dynamically (use process.arch and
process.platform like the AUDIO_CAPTURE_NODE_PATH/IMAGE_PROCESSOR_NODE_PATH
entries) so the join(repoRoot, ...) paths point to the correct prebuild/vendor
subfolders for the current platform; ensure any macOS-only modules still guard
runtime loads with process.platform checks and that the join and repoRoot
symbols remain used for path assembly.
| 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`); | ||
|
|
There was a problem hiding this comment.
Hardcoded SDK version will break on upgrades.
The SDK path includes a pinned version string @0.2.87+3c5d820c62823f0b that will become invalid when the dependency is upgraded. This pattern appears in multiple files:
compile.ts:85compile.ts:136-137src/utils/ripgrepAsset.ts:27-29
♻️ Proposed fix using dynamic resolution
+import { resolve as resolvePath } from 'path'
+
+// Dynamically resolve SDK path using require.resolve
+function getSdkPath(): string {
+ try {
+ // This resolves through node_modules correctly
+ const sdkMain = require.resolve('@anthropic-ai/claude-agent-sdk')
+ return resolvePath(sdkMain, '..')
+ } catch {
+ throw new Error('Could not resolve `@anthropic-ai/claude-agent-sdk`')
+ }
+}
+
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 sdkRoot = getSdkPath()
+ const rgCache = join(sdkRoot, 'vendor/ripgrep')🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@compile.ts` around lines 83 - 86, The hardcoded vendor path in
generateRipgrepAsset (and the other occurrences) will break on upgrades; replace
the literal join(...) that embeds `@anthropic-ai+claude-agent-sdk@0.2.87+...`
with dynamic module resolution (e.g., use require.resolve or import.meta.resolve
to locate the package root and then append `/vendor/ripgrep`), so the code
discovers the installed `@anthropic-ai/claude-agent-sdk` location at runtime;
apply the same change to the other occurrences (the generateRipgrepAsset
function and the references in src/utils/ripgrepAsset.ts) so they all derive the
SDK path dynamically instead of using a pinned version string.
| 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)"); | ||
| } |
There was a problem hiding this comment.
Fragile regex patching of minified SDK code.
Patching minified JavaScript using regex patterns like Uy_ and dy_=Uy_ is extremely brittle. Any SDK update that changes minification output, variable naming, or code structure will silently break this patch. The warning at line 148-149 only logs; the build continues with potentially broken ripgrep resolution.
Consider:
- Failing the build if patch doesn't match instead of just warning
- Using AST-based patching for robustness
- Documenting the exact SDK version this targets and adding version checks
🛡️ Proposed fix to fail on patch mismatch
if (patchedSdk === sdkContent) {
- console.warn("Warning: SDK patch did not match");
+ console.error("ERROR: SDK patch did not match. The SDK may have been updated.");
+ console.error("Expected patterns: 'import{fileURLToPath as Uy_}from\"url\"' and 'dy_=Uy_(import.meta.url)'");
+ exit(1);
} else {📝 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.
| 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)"); | |
| } | |
| if (patchedSdk === sdkContent) { | |
| console.error("ERROR: SDK patch did not match. The SDK may have been updated."); | |
| console.error("Expected patterns: 'import{fileURLToPath as Uy_}from\"url\"' and 'dy_=Uy_(import.meta.url)'"); | |
| process.exit(1); | |
| } else { | |
| await writeFile(sdkCachePath, patchedSdk); | |
| console.log("Patched SDK cli.js (bun cache)"); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@compile.ts` around lines 139 - 153, The regex-based patching of the minified
SDK (operating on sdkContent to produce patchedSdk using patterns matching Uy_
and dy_=Uy_) is brittle and currently only emits a console.warn if no
replacement occurred; change this to fail the build by throwing or exiting with
a non-zero status when patchedSdk === sdkContent so the CI stops on mismatch,
and add a clear error message including sdkCachePath and the original fragment
names (Uy_, dy_=Uy_) to aid debugging; additionally, replace this fragile
approach in the long term by switching the patching logic from regex to an
AST-based transform (or at minimum add an SDK version check and a documented
targeted version comment near the patch code and function writeFile usage) so
future minifier/name changes are detected and handled safely.
|
|
||
| const RG_VERSION = '15.0.1' | ||
| const BASE_URL = `https://github.com/microsoft/ripgrep-prebuilt/releases/download/v${RG_VERSION}` | ||
| const BASE_URL = `https://gh-proxy.com/github.com/microsoft/ripgrep-prebuilt/releases/download/v${RG_VERSION}` |
There was a problem hiding this comment.
Supply chain risk: using third-party GitHub proxy.
Using gh-proxy.com introduces a man-in-the-middle vector. If this proxy is compromised, malicious binaries could be served. Consider:
- Using the official GitHub URL (
https://github.com/microsoft/ripgrep-prebuilt/releases/download/...) - If proxy is needed for regional access, add checksum verification after download
🔒 Proposed fix using official GitHub URL
-const BASE_URL = `https://gh-proxy.com/github.com/microsoft/ripgrep-prebuilt/releases/download/v${RG_VERSION}`
+const BASE_URL = `https://github.com/microsoft/ripgrep-prebuilt/releases/download/v${RG_VERSION}`If regional access is needed, consider adding SHA256 verification:
const EXPECTED_CHECKSUMS: Record<string, string> = {
'ripgrep-v15.0.1-aarch64-apple-darwin.tar.gz': '<sha256>',
// ... other platforms
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/download-ripgrep.ts` at line 21, Replace the third‑party proxy
BASE_URL (`BASE_URL = ...gh-proxy.com...`) to use the official GitHub release
URL constructed with RG_VERSION, and add SHA256 checksum verification after the
file is downloaded: create an EXPECTED_CHECKSUMS mapping keyed by the release
asset filename, compute the downloaded file's SHA256, compare against
EXPECTED_CHECKSUMS entry, and abort (throw/log and exit) if the checksum does
not match before any extraction or execution; update the download/verify flow
where the asset is fetched to perform this check.
| /** | ||
| * 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 |
There was a problem hiding this comment.
Docstring is inconsistent with implementation.
The comment says "Primary check: Bun.embeddedFiles" and "Fallback: BUNDLED_MODE", but the implementation does the opposite—BUNDLED_MODE is checked first (line 21) and Bun.embeddedFiles is the fallback.
📝 Proposed fix for docstring accuracy
/**
* Detects if running as a Bun-compiled standalone executable.
*
- * 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 with assets).
*/📝 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.
| /** | |
| * 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 | |
| /** | |
| * Detects if running as a Bun-compiled standalone executable. | |
| * | |
| * Primary check: BUNDLED_MODE compile-time constant injected by compile.ts. | |
| * Fallback: Bun.embeddedFiles (present in compiled binaries with assets). | |
| */ | |
| // 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 |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/utils/bundledMode.ts` around lines 12 - 21, The docstring for
isInBundledMode is inconsistent with the implementation: the code checks the
compile-time BUNDLED_MODE first and Bun.embeddedFiles second, so update the
comment to state "Primary check: BUNDLED_MODE (compile-time constant) and
Fallback: Bun.embeddedFiles" and adjust any wording to reference the
BUNDLED_MODE declaration and the isInBundledMode function so the comment
accurately reflects the code flow.
| // 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: [] } |
There was a problem hiding this comment.
Bundled mode now displays as "vendor" in Doctor UI.
Per context snippet 2 (src/screens/Doctor.tsx:313-320), the Doctor diagnostic maps modes:
"embedded"→"bundled"(old behavior)"builtin"→"vendor"(new behavior)
With this change, compiled executables will show as "vendor" rather than "bundled", which may confuse users expecting to see bundled mode indicated. Consider whether the Doctor UI mapping should be updated or if a distinct mode value is needed.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/utils/ripgrep.ts` around lines 48 - 52, The current bundled extraction
branch in isInBundledMode() returns mode: 'builtin' (see getRipgrepBinaryPath()
usage), which now maps to "vendor" in the Doctor UI and misrepresents compiled
executables; change the returned mode value from 'builtin' to a distinct value
that preserves the previous UI label (e.g., 'embedded' or 'bundled') or add a
new mode like 'bundled' so Doctor.tsx's mapping (which currently translates
'embedded'→'bundled' and 'builtin'→'vendor') can correctly display
compiled/embedded ripgrep; update the return in the bundled branch to use that
chosen mode and adjust any consumers expecting 'builtin' as needed.
| 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' | ||
| } |
There was a problem hiding this comment.
Windows ARM64 architecture not supported.
getPlatformKey() always returns 'windows_x64' for Windows regardless of process.arch. ARM64 Windows devices (e.g., Surface Pro X, newer Qualcomm-based laptops) would attempt to run an x64 binary, which may fail or run under emulation with degraded performance.
♻️ Proposed fix to support Windows ARM64
function getPlatformKey(): string {
const platform = getPlatform()
const arch = process.arch
- if (platform === 'windows') return 'windows_x64'
+ if (platform === 'windows') return arch === 'arm64' ? 'windows_arm64' : 'windows_x64'
if (platform === 'macos') return arch === 'arm64' ? 'darwin_arm64' : 'darwin_x64'
return arch === 'arm64' ? 'linux_arm64' : 'linux_x64'
}Note: This also requires adding 'windows_arm64' to allPlatforms in compile.ts and sourcing the ARM64 ripgrep binary.
📝 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.
| 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' | |
| } | |
| function getPlatformKey(): string { | |
| const platform = getPlatform() | |
| const arch = process.arch | |
| if (platform === 'windows') return arch === 'arm64' ? 'windows_arm64' : 'windows_x64' | |
| if (platform === 'macos') return arch === 'arm64' ? 'darwin_arm64' : 'darwin_x64' | |
| return arch === 'arm64' ? 'linux_arm64' : 'linux_x64' | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/utils/ripgrepAsset.ts` around lines 32 - 38, getPlatformKey currently
always returns 'windows_x64' for Windows and ignores process.arch, breaking
Windows ARM64 support; update getPlatformKey (which calls getPlatform and uses
process.arch) to return 'windows_arm64' when getPlatform() === 'windows' and
process.arch === 'arm64', otherwise keep existing mappings for darwin and linux;
after this change also add 'windows_arm64' to the allPlatforms list in
compile.ts and ensure the ARM64 ripgrep binary is sourced for packaging.
| // 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 | ||
| } |
There was a problem hiding this comment.
Cache validation doesn't verify binary integrity.
The cache check at line 99 calls readFileSync(filePath) but doesn't verify the returned buffer is non-empty or valid. An empty or corrupted file would pass the existence check, and the function would return a path to a broken binary.
🛡️ Proposed fix for stricter cache validation
try {
const storedTag = readFileSync(versionPath, 'utf8')
- if (storedTag === versionTag && readFileSync(filePath)) {
+ const fileContent = readFileSync(filePath)
+ // Verify version match and non-empty binary
+ if (storedTag === versionTag && fileContent.length > 0) {
extractedPaths[key] = filePath
return filePath
}
} catch {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/utils/ripgrepAsset.ts` around lines 96 - 105, The cache fast-path returns
filePath after calling readFileSync(filePath) without verifying the file
contents; change the check so that after confirming storedTag === versionTag you
read the file into a variable (using readFileSync) and validate it is non-empty
(size/length > 0) and ideally stat the file (fs.statSync(filePath)) to ensure
size > 0 and executable permission before setting extractedPaths[key] = filePath
and returning; on any validation failure fall through to the extraction path and
keep the existing try/catch behavior.
writeFileSync defaults to 0o644 which lacks execute permission, causing EACCES errors when Claude Code tries to spawn ripgrep.
|
@realorange1994 可能我们不会以 base64 这种嵌入方式进行处理, 一个多平台原因, 一个是有现成的 vscode 的构建版本 |
改动
compile.ts构建脚本和ripgrepAsset.ts,将 ripgrep 二进制以 base64 编码嵌入独立可执行文件argv0派发改为 base64 二进制提取(与官方方案一致)bundledMode.ts新增BUNDLED_MODE编译时常量作为备用检测方式download-ripgrep.ts脚本用于下载 ripgrep 二进制背景
此前 bundled 模式下,grep/glob 工具依赖 bun 的
argv0='rg'派发机制,假设 ripgrep 已静态编译进 bun二进制中。这种方式不够可靠,且与官方 Claude Code 的实现不一致。
本 PR 切换为官方方案:将 ripgrep 二进制编码为 base64(
ripgrepAssetBase64.ts),运行时通过getRipgrepBinaryPath()解压到临时目录直接执行,使 bundled 可执行文件中的 grep/glob 工具完全自包含,无需依赖系统
rg。文件变更
compile.ts— 构建打包脚本src/utils/ripgrepAsset.ts— 二进制提取逻辑src/utils/ripgrepAssetBase64.ts— base64 编码的 rg 二进制占位文件src/utils/ripgrep.ts—getRipgrepConfig()改为返回builtin模式 + 提取路径src/utils/bundledMode.ts— 新增BUNDLED_MODE编译时常量备用检测scripts/download-ripgrep.ts— 下载脚本修复package.json— 新增依赖测试
bun run build构建成功rg即可正常使用 grep 工具rg仍正常工作Summary by CodeRabbit
New Features
Improvements