-
Notifications
You must be signed in to change notification settings - Fork 13.6k
feat: bundled 模式改用 base64 嵌入 ripgrep 二进制替代 argv0 派发 #113
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
Changes from all commits
7eab0ff
f3df181
3e6797a
00b9a68
b1ab2be
fb86afa
ebc9523
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`); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+83
to
+86
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. Hardcoded SDK version will break on upgrades. The SDK path includes a pinned version string
♻️ 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 |
||||||||||||||||||||||||||||||||||||||||||||||||
| 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}`); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| // 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
+139
to
+153
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. Fragile regex patching of minified SDK code. Patching minified JavaScript using regex patterns like Consider:
🛡️ 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
Suggested change
🤖 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 |
|---|---|---|
|
|
@@ -18,7 +18,7 @@ const __filename = fileURLToPath(import.meta.url) | |
| const __dirname = path.dirname(__filename) | ||
|
|
||
| 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. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Supply chain risk: using third-party GitHub proxy. Using
🔒 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 |
||
|
|
||
| // --- Platform mapping --- | ||
|
|
||
|
|
||
| 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 | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
12
to
+21
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. Docstring is inconsistent with implementation. The comment says "Primary check: Bun.embeddedFiles" and "Fallback: BUNDLED_MODE", but the implementation does the opposite— 📝 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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||
| 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
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. Bundled mode now displays as "vendor" in Doctor UI. Per context snippet 2 (
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 |
||
| } | ||
|
|
||
| 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') | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| 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' | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
Comment on lines
+32
to
+38
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. Windows ARM64 architecture not supported.
♻️ 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 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // 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 | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // 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 | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
Comment on lines
+96
to
+105
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. Cache validation doesn't verify binary integrity. The cache check at line 99 calls 🛡️ 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 |
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // Decode and extract | ||||||||||||||||||||||||||||||
| mkdirSync(tmpDir, { recursive: true }) | ||||||||||||||||||||||||||||||
| const buffer = Buffer.from(base64Data, 'base64') | ||||||||||||||||||||||||||||||
| writeFileSync(filePath, buffer, { mode: 0o755 }) | ||||||||||||||||||||||||||||||
| writeFileSync(versionPath, versionTag, 'utf8') | ||||||||||||||||||||||||||||||
| 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.
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, andURL_HANDLER_NODE_PATHare hardcoded toarm64-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
🤖 Prompt for AI Agents