refactor(grep): replace glob dependency with fs.readdirSync
- Add findFileRecursive function using native Node.js fs API - Remove glob package from dependencies - Add unit tests for findFileRecursive 🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
This commit is contained in:
15
bun.lock
15
bun.lock
@@ -10,7 +10,6 @@
|
|||||||
"@code-yeongyu/comment-checker": "^0.5.0",
|
"@code-yeongyu/comment-checker": "^0.5.0",
|
||||||
"@openauthjs/openauth": "^0.4.3",
|
"@openauthjs/openauth": "^0.4.3",
|
||||||
"@opencode-ai/plugin": "^1.0.150",
|
"@opencode-ai/plugin": "^1.0.150",
|
||||||
"glob": "^13.0.0",
|
|
||||||
"hono": "^4.10.4",
|
"hono": "^4.10.4",
|
||||||
"picomatch": "^4.0.2",
|
"picomatch": "^4.0.2",
|
||||||
"xdg-basedir": "^5.1.0",
|
"xdg-basedir": "^5.1.0",
|
||||||
@@ -71,10 +70,6 @@
|
|||||||
|
|
||||||
"@code-yeongyu/comment-checker": ["@code-yeongyu/comment-checker@0.5.0", "", { "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ], "bin": { "comment-checker": "bin/comment-checker" } }, "sha512-rKD2qQnTVUacsVQtpu3I5Sxi09X/XpOwS9fcmbUv1yfUL6llraaPuLmmxMBMRcmm7Zu31yEPVKCeUkVODfRL1g=="],
|
"@code-yeongyu/comment-checker": ["@code-yeongyu/comment-checker@0.5.0", "", { "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ], "bin": { "comment-checker": "bin/comment-checker" } }, "sha512-rKD2qQnTVUacsVQtpu3I5Sxi09X/XpOwS9fcmbUv1yfUL6llraaPuLmmxMBMRcmm7Zu31yEPVKCeUkVODfRL1g=="],
|
||||||
|
|
||||||
"@isaacs/balanced-match": ["@isaacs/balanced-match@4.0.1", "", {}, "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="],
|
|
||||||
|
|
||||||
"@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.0", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA=="],
|
|
||||||
|
|
||||||
"@openauthjs/openauth": ["@openauthjs/openauth@0.4.3", "", { "dependencies": { "@standard-schema/spec": "1.0.0-beta.3", "aws4fetch": "1.0.20", "jose": "5.9.6" }, "peerDependencies": { "arctic": "^2.2.2", "hono": "^4.0.0" } }, "sha512-RlnjqvHzqcbFVymEwhlUEuac4utA5h4nhSK/i2szZuQmxTIqbGUxZ+nM+avM+VV4Ing+/ZaNLKILoXS3yrkOOw=="],
|
"@openauthjs/openauth": ["@openauthjs/openauth@0.4.3", "", { "dependencies": { "@standard-schema/spec": "1.0.0-beta.3", "aws4fetch": "1.0.20", "jose": "5.9.6" }, "peerDependencies": { "arctic": "^2.2.2", "hono": "^4.0.0" } }, "sha512-RlnjqvHzqcbFVymEwhlUEuac4utA5h4nhSK/i2szZuQmxTIqbGUxZ+nM+avM+VV4Ing+/ZaNLKILoXS3yrkOOw=="],
|
||||||
|
|
||||||
"@opencode-ai/plugin": ["@opencode-ai/plugin@1.0.150", "", { "dependencies": { "@opencode-ai/sdk": "1.0.150", "zod": "4.1.8" } }, "sha512-XmY3yydk120GBv2KeLxSZlElFx4Zx9TYLa3bS9X1TxXot42UeoMLEi3Xa46yboYnWwp4bC9Fu+Gd1E7hypG8Jw=="],
|
"@opencode-ai/plugin": ["@opencode-ai/plugin@1.0.150", "", { "dependencies": { "@opencode-ai/sdk": "1.0.150", "zod": "4.1.8" } }, "sha512-XmY3yydk120GBv2KeLxSZlElFx4Zx9TYLa3bS9X1TxXot42UeoMLEi3Xa46yboYnWwp4bC9Fu+Gd1E7hypG8Jw=="],
|
||||||
@@ -129,22 +124,12 @@
|
|||||||
|
|
||||||
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||||
|
|
||||||
"glob": ["glob@13.0.0", "", { "dependencies": { "minimatch": "^10.1.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA=="],
|
|
||||||
|
|
||||||
"hono": ["hono@4.10.8", "", {}, "sha512-DDT0A0r6wzhe8zCGoYOmMeuGu3dyTAE40HHjwUsWFTEy5WxK1x2WDSsBPlEXgPbRIFY6miDualuUDbasPogIww=="],
|
"hono": ["hono@4.10.8", "", {}, "sha512-DDT0A0r6wzhe8zCGoYOmMeuGu3dyTAE40HHjwUsWFTEy5WxK1x2WDSsBPlEXgPbRIFY6miDualuUDbasPogIww=="],
|
||||||
|
|
||||||
"jose": ["jose@5.9.6", "", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="],
|
"jose": ["jose@5.9.6", "", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="],
|
||||||
|
|
||||||
"lru-cache": ["lru-cache@11.2.4", "", {}, "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg=="],
|
|
||||||
|
|
||||||
"minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="],
|
|
||||||
|
|
||||||
"minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
|
|
||||||
|
|
||||||
"oh-my-opencode": ["oh-my-opencode@0.1.30", "", { "dependencies": { "@ast-grep/cli": "^0.40.0", "@ast-grep/napi": "^0.40.0", "@code-yeongyu/comment-checker": "^0.4.1", "@opencode-ai/plugin": "^1.0.7", "xdg-basedir": "^5.1.0", "zod": "^4.1.8" }, "peerDependencies": { "bun": ">=1.0.0" } }, "sha512-pXGGgL/7Jcz3yuGJJTI72BKern2egwfRz2LQZTBq+jl+pNCybOvGvXtFmR+WGlF8O3ZjL1wIHypBbIVuHOBzxg=="],
|
"oh-my-opencode": ["oh-my-opencode@0.1.30", "", { "dependencies": { "@ast-grep/cli": "^0.40.0", "@ast-grep/napi": "^0.40.0", "@code-yeongyu/comment-checker": "^0.4.1", "@opencode-ai/plugin": "^1.0.7", "xdg-basedir": "^5.1.0", "zod": "^4.1.8" }, "peerDependencies": { "bun": ">=1.0.0" } }, "sha512-pXGGgL/7Jcz3yuGJJTI72BKern2egwfRz2LQZTBq+jl+pNCybOvGvXtFmR+WGlF8O3ZjL1wIHypBbIVuHOBzxg=="],
|
||||||
|
|
||||||
"path-scurry": ["path-scurry@2.0.1", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA=="],
|
|
||||||
|
|
||||||
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||||
|
|
||||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||||
|
|||||||
@@ -51,7 +51,6 @@
|
|||||||
"@code-yeongyu/comment-checker": "^0.5.0",
|
"@code-yeongyu/comment-checker": "^0.5.0",
|
||||||
"@openauthjs/openauth": "^0.4.3",
|
"@openauthjs/openauth": "^0.4.3",
|
||||||
"@opencode-ai/plugin": "^1.0.150",
|
"@opencode-ai/plugin": "^1.0.150",
|
||||||
"glob": "^13.0.0",
|
|
||||||
"hono": "^4.10.4",
|
"hono": "^4.10.4",
|
||||||
"picomatch": "^4.0.2",
|
"picomatch": "^4.0.2",
|
||||||
"xdg-basedir": "^5.1.0",
|
"xdg-basedir": "^5.1.0",
|
||||||
|
|||||||
103
src/tools/grep/downloader.test.ts
Normal file
103
src/tools/grep/downloader.test.ts
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import { describe, test, expect, beforeEach, afterEach } from "bun:test"
|
||||||
|
import { mkdirSync, rmSync, writeFileSync, existsSync } from "node:fs"
|
||||||
|
import { join } from "node:path"
|
||||||
|
import { tmpdir } from "node:os"
|
||||||
|
|
||||||
|
// Import the function we'll create to replace glob
|
||||||
|
import { findFileRecursive } from "./downloader"
|
||||||
|
|
||||||
|
describe("findFileRecursive", () => {
|
||||||
|
let testDir: string
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// #given - create temp directory for testing
|
||||||
|
testDir = join(tmpdir(), `downloader-test-${Date.now()}`)
|
||||||
|
mkdirSync(testDir, { recursive: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// cleanup
|
||||||
|
if (existsSync(testDir)) {
|
||||||
|
rmSync(testDir, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should find file in root directory", () => {
|
||||||
|
// #given
|
||||||
|
const targetFile = join(testDir, "rg.exe")
|
||||||
|
writeFileSync(targetFile, "dummy content")
|
||||||
|
|
||||||
|
// #when
|
||||||
|
const result = findFileRecursive(testDir, "rg.exe")
|
||||||
|
|
||||||
|
// #then
|
||||||
|
expect(result).toBe(targetFile)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should find file in nested directory (ripgrep release structure)", () => {
|
||||||
|
// #given - simulate ripgrep release zip structure
|
||||||
|
const nestedDir = join(testDir, "ripgrep-14.1.1-x86_64-pc-windows-msvc")
|
||||||
|
mkdirSync(nestedDir, { recursive: true })
|
||||||
|
const targetFile = join(nestedDir, "rg.exe")
|
||||||
|
writeFileSync(targetFile, "dummy content")
|
||||||
|
|
||||||
|
// #when
|
||||||
|
const result = findFileRecursive(testDir, "rg.exe")
|
||||||
|
|
||||||
|
// #then
|
||||||
|
expect(result).toBe(targetFile)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should find file in deeply nested directory", () => {
|
||||||
|
// #given
|
||||||
|
const deepDir = join(testDir, "level1", "level2", "level3")
|
||||||
|
mkdirSync(deepDir, { recursive: true })
|
||||||
|
const targetFile = join(deepDir, "rg")
|
||||||
|
writeFileSync(targetFile, "dummy content")
|
||||||
|
|
||||||
|
// #when
|
||||||
|
const result = findFileRecursive(testDir, "rg")
|
||||||
|
|
||||||
|
// #then
|
||||||
|
expect(result).toBe(targetFile)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should return null when file not found", () => {
|
||||||
|
// #given - empty directory
|
||||||
|
|
||||||
|
// #when
|
||||||
|
const result = findFileRecursive(testDir, "nonexistent.exe")
|
||||||
|
|
||||||
|
// #then
|
||||||
|
expect(result).toBeNull()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should find first match when multiple files exist", () => {
|
||||||
|
// #given
|
||||||
|
const dir1 = join(testDir, "dir1")
|
||||||
|
const dir2 = join(testDir, "dir2")
|
||||||
|
mkdirSync(dir1, { recursive: true })
|
||||||
|
mkdirSync(dir2, { recursive: true })
|
||||||
|
writeFileSync(join(dir1, "rg"), "first")
|
||||||
|
writeFileSync(join(dir2, "rg"), "second")
|
||||||
|
|
||||||
|
// #when
|
||||||
|
const result = findFileRecursive(testDir, "rg")
|
||||||
|
|
||||||
|
// #then
|
||||||
|
expect(result).not.toBeNull()
|
||||||
|
expect(result!.endsWith("rg")).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should match exact filename, not partial", () => {
|
||||||
|
// #given
|
||||||
|
writeFileSync(join(testDir, "rg.exe.bak"), "backup file")
|
||||||
|
writeFileSync(join(testDir, "not-rg.exe"), "wrong file")
|
||||||
|
|
||||||
|
// #when
|
||||||
|
const result = findFileRecursive(testDir, "rg.exe")
|
||||||
|
|
||||||
|
// #then
|
||||||
|
expect(result).toBeNull()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,7 +1,21 @@
|
|||||||
import { existsSync, mkdirSync, chmodSync, unlinkSync } from "node:fs"
|
import { existsSync, mkdirSync, chmodSync, unlinkSync, readdirSync } from "node:fs"
|
||||||
import { join } from "node:path"
|
import { join } from "node:path"
|
||||||
import { spawn } from "bun"
|
import { spawn } from "bun"
|
||||||
|
|
||||||
|
export function findFileRecursive(dir: string, filename: string): string | null {
|
||||||
|
try {
|
||||||
|
const entries = readdirSync(dir, { withFileTypes: true, recursive: true })
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (entry.isFile() && entry.name === filename) {
|
||||||
|
return join(entry.parentPath ?? dir, entry.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
const RG_VERSION = "14.1.1"
|
const RG_VERSION = "14.1.1"
|
||||||
|
|
||||||
const PLATFORM_CONFIG: Record<string, { platform: string; extension: "tar.gz" | "zip" } | undefined> = {
|
const PLATFORM_CONFIG: Record<string, { platform: string; extension: "tar.gz" | "zip" } | undefined> = {
|
||||||
@@ -70,14 +84,12 @@ async function extractZipWindows(archivePath: string, destDir: string): Promise<
|
|||||||
throw new Error("Failed to extract zip with PowerShell")
|
throw new Error("Failed to extract zip with PowerShell")
|
||||||
}
|
}
|
||||||
|
|
||||||
const { globSync } = await import("glob")
|
const foundPath = findFileRecursive(destDir, "rg.exe")
|
||||||
const rgFiles = globSync("**/rg.exe", { cwd: destDir })
|
if (foundPath) {
|
||||||
if (rgFiles.length > 0) {
|
|
||||||
const srcPath = join(destDir, rgFiles[0])
|
|
||||||
const destPath = join(destDir, "rg.exe")
|
const destPath = join(destDir, "rg.exe")
|
||||||
if (srcPath !== destPath) {
|
if (foundPath !== destPath) {
|
||||||
const { renameSync } = await import("node:fs")
|
const { renameSync } = await import("node:fs")
|
||||||
renameSync(srcPath, destPath)
|
renameSync(foundPath, destPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,14 +104,12 @@ async function extractZipUnix(archivePath: string, destDir: string): Promise<voi
|
|||||||
throw new Error("Failed to extract zip")
|
throw new Error("Failed to extract zip")
|
||||||
}
|
}
|
||||||
|
|
||||||
const { globSync } = await import("glob")
|
const foundPath = findFileRecursive(destDir, "rg")
|
||||||
const rgFiles = globSync("**/rg", { cwd: destDir })
|
if (foundPath) {
|
||||||
if (rgFiles.length > 0) {
|
|
||||||
const srcPath = join(destDir, rgFiles[0])
|
|
||||||
const destPath = join(destDir, "rg")
|
const destPath = join(destDir, "rg")
|
||||||
if (srcPath !== destPath) {
|
if (foundPath !== destPath) {
|
||||||
const { renameSync } = await import("node:fs")
|
const { renameSync } = await import("node:fs")
|
||||||
renameSync(srcPath, destPath)
|
renameSync(foundPath, destPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user