fix: improve glob tool Windows compatibility and rg resolution (#309)
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { spawn } from "bun"
|
||||
import {
|
||||
resolveGrepCli,
|
||||
type GrepBackend,
|
||||
DEFAULT_TIMEOUT_MS,
|
||||
DEFAULT_LIMIT,
|
||||
DEFAULT_MAX_DEPTH,
|
||||
@@ -10,6 +11,11 @@ import {
|
||||
import type { GlobOptions, GlobResult, FileMatch } from "./types"
|
||||
import { stat } from "node:fs/promises"
|
||||
|
||||
export interface ResolvedCli {
|
||||
path: string
|
||||
backend: GrepBackend
|
||||
}
|
||||
|
||||
function buildRgArgs(options: GlobOptions): string[] {
|
||||
const args: string[] = [
|
||||
...RG_FILES_FLAGS,
|
||||
@@ -40,6 +46,25 @@ function buildFindArgs(options: GlobOptions): string[] {
|
||||
return args
|
||||
}
|
||||
|
||||
function buildPowerShellCommand(options: GlobOptions): string[] {
|
||||
const maxDepth = Math.min(options.maxDepth ?? DEFAULT_MAX_DEPTH, DEFAULT_MAX_DEPTH)
|
||||
const paths = options.paths?.length ? options.paths : ["."]
|
||||
const searchPath = paths[0] || "."
|
||||
|
||||
const escapedPath = searchPath.replace(/'/g, "''")
|
||||
const escapedPattern = options.pattern.replace(/'/g, "''")
|
||||
|
||||
let psCommand = `Get-ChildItem -Path '${escapedPath}' -File -Recurse -Depth ${maxDepth - 1} -Filter '${escapedPattern}'`
|
||||
|
||||
if (options.hidden) {
|
||||
psCommand += " -Force"
|
||||
}
|
||||
|
||||
psCommand += " -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName"
|
||||
|
||||
return ["powershell", "-NoProfile", "-Command", psCommand]
|
||||
}
|
||||
|
||||
async function getFileMtime(filePath: string): Promise<number> {
|
||||
try {
|
||||
const stats = await stat(filePath)
|
||||
@@ -49,25 +74,40 @@ async function getFileMtime(filePath: string): Promise<number> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function runRgFiles(options: GlobOptions): Promise<GlobResult> {
|
||||
const cli = resolveGrepCli()
|
||||
export async function runRgFiles(
|
||||
options: GlobOptions,
|
||||
resolvedCli?: ResolvedCli
|
||||
): Promise<GlobResult> {
|
||||
const cli = resolvedCli ?? resolveGrepCli()
|
||||
const timeout = Math.min(options.timeout ?? DEFAULT_TIMEOUT_MS, DEFAULT_TIMEOUT_MS)
|
||||
const limit = Math.min(options.limit ?? DEFAULT_LIMIT, DEFAULT_LIMIT)
|
||||
|
||||
const isRg = cli.backend === "rg"
|
||||
const args = isRg ? buildRgArgs(options) : buildFindArgs(options)
|
||||
const isWindows = process.platform === "win32"
|
||||
|
||||
let command: string[]
|
||||
let cwd: string | undefined
|
||||
|
||||
const paths = options.paths?.length ? options.paths : ["."]
|
||||
if (isRg) {
|
||||
const args = buildRgArgs(options)
|
||||
const paths = options.paths?.length ? options.paths : ["."]
|
||||
args.push(...paths)
|
||||
command = [cli.path, ...args]
|
||||
cwd = undefined
|
||||
} else if (isWindows) {
|
||||
command = buildPowerShellCommand(options)
|
||||
cwd = undefined
|
||||
} else {
|
||||
const args = buildFindArgs(options)
|
||||
const paths = options.paths?.length ? options.paths : ["."]
|
||||
cwd = paths[0] || "."
|
||||
command = [cli.path, ...args]
|
||||
}
|
||||
|
||||
const cwd = paths[0] || "."
|
||||
|
||||
const proc = spawn([cli.path, ...args], {
|
||||
const proc = spawn(command, {
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
cwd: isRg ? undefined : cwd,
|
||||
cwd,
|
||||
})
|
||||
|
||||
const timeoutPromise = new Promise<never>((_, reject) => {
|
||||
@@ -106,7 +146,15 @@ export async function runRgFiles(options: GlobOptions): Promise<GlobResult> {
|
||||
break
|
||||
}
|
||||
|
||||
const filePath = isRg ? line : `${cwd}/${line}`
|
||||
let filePath: string
|
||||
if (isRg) {
|
||||
filePath = line
|
||||
} else if (isWindows) {
|
||||
filePath = line.trim()
|
||||
} else {
|
||||
filePath = `${cwd}/${line}`
|
||||
}
|
||||
|
||||
const mtime = await getFileMtime(filePath)
|
||||
files.push({ path: filePath, mtime })
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export { resolveGrepCli, type GrepBackend } from "../grep/constants"
|
||||
export { resolveGrepCli, resolveGrepCliWithAutoInstall, type GrepBackend } from "../grep/constants"
|
||||
|
||||
export const DEFAULT_TIMEOUT_MS = 60_000
|
||||
export const DEFAULT_LIMIT = 100
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool"
|
||||
import { runRgFiles } from "./cli"
|
||||
import { resolveGrepCliWithAutoInstall } from "./constants"
|
||||
import { formatGlobResult } from "./utils"
|
||||
|
||||
export const glob: ToolDefinition = tool({
|
||||
@@ -21,12 +22,16 @@ export const glob: ToolDefinition = tool({
|
||||
},
|
||||
execute: async (args) => {
|
||||
try {
|
||||
const cli = await resolveGrepCliWithAutoInstall()
|
||||
const paths = args.path ? [args.path] : undefined
|
||||
|
||||
const result = await runRgFiles({
|
||||
pattern: args.pattern,
|
||||
paths,
|
||||
})
|
||||
const result = await runRgFiles(
|
||||
{
|
||||
pattern: args.pattern,
|
||||
paths,
|
||||
},
|
||||
cli
|
||||
)
|
||||
|
||||
return formatGlobResult(result)
|
||||
} catch (e) {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { existsSync } from "node:fs"
|
||||
import { join, dirname } from "node:path"
|
||||
import { spawnSync } from "node:child_process"
|
||||
import { getInstalledRipgrepPath, downloadAndInstallRipgrep } from "./downloader"
|
||||
import { getDataDir } from "../../shared/data-path"
|
||||
|
||||
export type GrepBackend = "rg" | "grep"
|
||||
|
||||
@@ -36,6 +37,9 @@ function getOpenCodeBundledRg(): string | null {
|
||||
const rgName = isWindows ? "rg.exe" : "rg"
|
||||
|
||||
const candidates = [
|
||||
// OpenCode XDG data path (highest priority - where OpenCode installs rg)
|
||||
join(getDataDir(), "opencode", "bin", rgName),
|
||||
// Legacy paths relative to execPath
|
||||
join(execDir, rgName),
|
||||
join(execDir, "bin", rgName),
|
||||
join(execDir, "..", "bin", rgName),
|
||||
|
||||
Reference in New Issue
Block a user