From 6e4236bf7681b2a2cba5630a8dd0a0d5a200a0df Mon Sep 17 00:00:00 2001 From: joshualitt Date: Fri, 19 Sep 2025 08:13:28 -0700 Subject: [PATCH] feat(third_party) Port `get-ripgrep`. (#8514) --- .gitignore | 1 + esbuild.config.js | 4 + npm-shrinkwrap.json | 115 ++++++------ package.json | 1 + packages/core/package.json | 2 +- packages/core/src/tools/ripGrep.test.ts | 4 +- packages/core/src/tools/ripGrep.ts | 2 +- packages/core/tsconfig.json | 3 +- scripts/build_package.js | 4 +- scripts/clean.js | 1 + third_party/get-ripgrep/package.json | 30 ++- .../get-ripgrep/src/downloadRipGrep.test.ts | 172 ++++++++++++++++++ ...{downloadRipGrep.js => downloadRipGrep.ts} | 61 +++---- third_party/get-ripgrep/src/index.js | 17 -- third_party/get-ripgrep/src/index.ts | 7 + third_party/get-ripgrep/tsconfig.json | 13 ++ 16 files changed, 308 insertions(+), 129 deletions(-) create mode 100644 third_party/get-ripgrep/src/downloadRipGrep.test.ts rename third_party/get-ripgrep/src/{downloadRipGrep.js => downloadRipGrep.ts} (61%) delete mode 100644 third_party/get-ripgrep/src/index.js create mode 100644 third_party/get-ripgrep/src/index.ts create mode 100644 third_party/get-ripgrep/tsconfig.json diff --git a/.gitignore b/.gitignore index d44f7ad349..84a90e86da 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,7 @@ bundle # Test report files junit.xml packages/*/coverage/ +third_party/*/coverage/ # Generated files packages/cli/src/generated/ diff --git a/esbuild.config.js b/esbuild.config.js index 3f1644a63e..d6df6e319a 100644 --- a/esbuild.config.js +++ b/esbuild.config.js @@ -41,6 +41,10 @@ esbuild format: 'esm', external, alias: { + 'get-ripgrep': path.resolve( + __dirname, + 'third_party/get-ripgrep/src/index.ts', + ), 'is-in-ci': path.resolve( __dirname, 'packages/cli/src/patches/is-in-ci.ts', diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 9ab80d6838..684a3a7910 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -8,6 +8,7 @@ "name": "@google/gemini-cli", "version": "0.7.0-nightly.20250918.2722473a", "workspaces": [ + "third_party/get-ripgrep", "packages/*" ], "dependencies": { @@ -397,13 +398,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", - "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", + "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.27.3" + "@babel/types": "^7.28.4" }, "bin": { "parser": "bin/babel-parser.js" @@ -423,9 +424,9 @@ } }, "node_modules/@babel/types": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", - "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", + "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1833,41 +1834,15 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@joshua.litt/get-ripgrep": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/@joshua.litt/get-ripgrep/-/get-ripgrep-0.0.2.tgz", - "integrity": "sha512-cSHA+H+HEkOXeiCxrNvGj/pgv2Y0bfp4GbH3R87zr7Vob2pDUZV3BkUL9ucHMoDFID4GteSy5z5niN/lF9QeuQ==", - "dependencies": { - "@lvce-editor/verror": "^1.6.0", - "execa": "^9.5.2", - "extract-zip": "^2.0.1", - "fs-extra": "^11.3.0", - "got": "^14.4.5", - "path-exists": "^5.0.0", - "xdg-basedir": "^5.1.0" - } - }, - "node_modules/@joshua.litt/get-ripgrep/node_modules/path-exists": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -1880,16 +1855,6 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", @@ -1898,9 +1863,9 @@ "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", "dependencies": { @@ -1990,12 +1955,6 @@ "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", "license": "MIT" }, - "node_modules/@lvce-editor/verror": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@lvce-editor/verror/-/verror-1.7.0.tgz", - "integrity": "sha512-+LGuAEIC2L7pbvkyAQVWM2Go0dAy+UWEui28g07zNtZsCBhm+gusBK8PNwLJLV5Jay+TyUYuwLIbJdjLLzqEBg==", - "license": "MIT" - }, "node_modules/@lydell/node-pty": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@lydell/node-pty/-/node-pty-1.1.0.tgz", @@ -8753,6 +8712,10 @@ "node": ">= 0.4" } }, + "node_modules/get-ripgrep": { + "resolved": "third_party/get-ripgrep", + "link": true + }, "node_modules/get-stream": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", @@ -17092,7 +17055,6 @@ "@google-cloud/opentelemetry-cloud-monitoring-exporter": "^0.21.0", "@google-cloud/opentelemetry-cloud-trace-exporter": "^3.0.0", "@google/genai": "1.16.0", - "@joshua.litt/get-ripgrep": "^0.0.2", "@modelcontextprotocol/sdk": "^1.11.0", "@opentelemetry/api": "^1.9.0", "@opentelemetry/exporter-logs-otlp-grpc": "^0.203.0", @@ -17116,6 +17078,7 @@ "fast-uri": "^3.0.6", "fdir": "^6.4.6", "fzf": "^0.5.2", + "get-ripgrep": "file:../../third_party/get-ripgrep", "glob": "^10.4.5", "google-auth-library": "^9.11.0", "html-to-text": "^9.0.5", @@ -17492,6 +17455,44 @@ "engines": { "node": ">= 0.6" } + }, + "third_party/get-ripgrep": { + "version": "0.0.0-dev", + "license": "MIT", + "dependencies": { + "execa": "^9.5.2", + "extract-zip": "^2.0.1", + "fs-extra": "^11.3.0", + "got": "^14.4.5", + "path-exists": "^5.0.0", + "xdg-basedir": "^5.1.0" + }, + "devDependencies": { + "@types/fs-extra": "^11.0.4", + "@types/node": "^22.13.0", + "prettier": "^3.4.2", + "typescript": "^5.7.3", + "vitest": "^3.1.1" + } + }, + "third_party/get-ripgrep/node_modules/@types/node": { + "version": "22.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.6.tgz", + "integrity": "sha512-r8uszLPpeIWbNKtvWRt/DbVi5zbqZyj1PTmhRMqBMvDnaz1QpmSKujUtJLrqGZeoM8v72MfYggDceY4K1itzWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "third_party/get-ripgrep/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } } } } diff --git a/package.json b/package.json index 133e7a4d08..5ba588a510 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ }, "type": "module", "workspaces": [ + "third_party/get-ripgrep", "packages/*" ], "private": "true", diff --git a/packages/core/package.json b/packages/core/package.json index 4556679efb..ce33c325eb 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -24,7 +24,6 @@ "@google-cloud/opentelemetry-cloud-monitoring-exporter": "^0.21.0", "@google-cloud/opentelemetry-cloud-trace-exporter": "^3.0.0", "@google-cloud/logging": "^11.2.1", - "@joshua.litt/get-ripgrep": "^0.0.2", "@modelcontextprotocol/sdk": "^1.11.0", "@opentelemetry/api": "^1.9.0", "@opentelemetry/exporter-logs-otlp-grpc": "^0.203.0", @@ -48,6 +47,7 @@ "fast-uri": "^3.0.6", "fdir": "^6.4.6", "fzf": "^0.5.2", + "get-ripgrep": "file:../../third_party/get-ripgrep", "glob": "^10.4.5", "google-auth-library": "^9.11.0", "html-to-text": "^9.0.5", diff --git a/packages/core/src/tools/ripGrep.test.ts b/packages/core/src/tools/ripGrep.test.ts index 7c47275b49..89a9631423 100644 --- a/packages/core/src/tools/ripGrep.test.ts +++ b/packages/core/src/tools/ripGrep.test.ts @@ -22,11 +22,11 @@ import type { Config } from '../config/config.js'; import { createMockWorkspaceContext } from '../test-utils/mockWorkspaceContext.js'; import type { ChildProcess } from 'node:child_process'; import { spawn } from 'node:child_process'; -import { downloadRipGrep } from '@joshua.litt/get-ripgrep'; +import { downloadRipGrep } from 'get-ripgrep'; import { fileExists } from '../utils/fileUtils.js'; // Mock dependencies for canUseRipgrep -vi.mock('@joshua.litt/get-ripgrep', () => ({ +vi.mock('get-ripgrep', () => ({ downloadRipGrep: vi.fn(), })); vi.mock('../utils/fileUtils.js', async (importOriginal) => { diff --git a/packages/core/src/tools/ripGrep.ts b/packages/core/src/tools/ripGrep.ts index 269fb37993..9b8cd6a0eb 100644 --- a/packages/core/src/tools/ripGrep.ts +++ b/packages/core/src/tools/ripGrep.ts @@ -8,7 +8,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { EOL } from 'node:os'; import { spawn } from 'node:child_process'; -import { downloadRipGrep } from '@joshua.litt/get-ripgrep'; +import { downloadRipGrep } from 'get-ripgrep'; import type { ToolInvocation, ToolResult } from './tools.js'; import { BaseDeclarativeTool, BaseToolInvocation, Kind } from './tools.js'; import { SchemaValidator } from '../utils/schemaValidator.js'; diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index b788af471a..c44f208142 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -7,5 +7,6 @@ "types": ["node", "vitest/globals"] }, "include": ["index.ts", "src/**/*.ts", "src/**/*.json"], - "exclude": ["node_modules", "dist"] + "exclude": ["node_modules", "dist"], + "references": [{ "path": "../../third_party/get-ripgrep" }] } diff --git a/scripts/build_package.js b/scripts/build_package.js index 73f73861e9..ab2d8f47bd 100644 --- a/scripts/build_package.js +++ b/scripts/build_package.js @@ -21,7 +21,9 @@ import { execSync } from 'node:child_process'; import { writeFileSync } from 'node:fs'; import { join } from 'node:path'; -if (!process.cwd().includes('packages')) { +if ( + !(process.cwd().includes('packages') || process.cwd().includes('third_party')) +) { console.error('must be invoked from a package directory'); process.exit(1); } diff --git a/scripts/clean.js b/scripts/clean.js index 88493a63c1..a66108c312 100644 --- a/scripts/clean.js +++ b/scripts/clean.js @@ -43,6 +43,7 @@ for (const workspace of rootPackageJson.workspaces) { for (const pkgPath of packages) { const pkgDir = dirname(join(root, pkgPath)); rmSync(join(pkgDir, 'dist'), RMRF_OPTIONS); + rmSync(join(pkgDir, 'tsconfig.tsbuildinfo'), RMRF_OPTIONS); } } diff --git a/third_party/get-ripgrep/package.json b/third_party/get-ripgrep/package.json index 80e01ae7ac..be69f8a30f 100644 --- a/third_party/get-ripgrep/package.json +++ b/third_party/get-ripgrep/package.json @@ -1,19 +1,20 @@ { - "name": "@lvce-editor/ripgrep", + "name": "get-ripgrep", "version": "0.0.0-dev", - "description": "A module for using ripgrep in a Node project", - "main": "src/index.js", - "typings": "src/index.d.ts", + "description": "A module for downloading ripgrep at runtime a Node project", + "main": "dist/index.js", + "files": [ + "dist" + ], "type": "module", - "repository": { - "type": "git", - "url": "https://github.com/lvce-editor/ripgrep" - }, "scripts": { - "postinstall": "node ./src/postinstall.js", - "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js", - "test:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch", - "format": "prettier --write ." + "build": "node ../../scripts/build_package.js", + "prepublishOnly": "npm run build", + "format": "prettier --write .", + "lint": "eslint . --ext .ts,.tsx", + "test": "vitest run", + "test:ci": "vitest run --coverage", + "typecheck": "tsc --noEmit" }, "keywords": [ "lvce-editor", @@ -22,20 +23,17 @@ "author": "Lvce Editor", "license": "MIT", "dependencies": { - "@lvce-editor/verror": "^1.6.0", "execa": "^9.5.2", "extract-zip": "^2.0.1", "fs-extra": "^11.3.0", "got": "^14.4.5", "path-exists": "^5.0.0", - "tempy": "^3.1.0", "xdg-basedir": "^5.1.0" }, "devDependencies": { "@types/fs-extra": "^11.0.4", - "@types/jest": "^29.5.14", "@types/node": "^22.13.0", - "jest": "^29.7.0", + "vitest": "^3.1.1", "prettier": "^3.4.2", "typescript": "^5.7.3" }, diff --git a/third_party/get-ripgrep/src/downloadRipGrep.test.ts b/third_party/get-ripgrep/src/downloadRipGrep.test.ts new file mode 100644 index 0000000000..31869944f8 --- /dev/null +++ b/third_party/get-ripgrep/src/downloadRipGrep.test.ts @@ -0,0 +1,172 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { Writable, Readable } from 'node:stream' +import fs from 'node:fs' +import { downloadRipGrep } from './downloadRipGrep.js' +import { execa } from 'execa' +import extractZip from 'extract-zip' +import fsExtra from 'fs-extra' +import got from 'got' +import * as os from 'node:os' +import { pathExists } from 'path-exists' +import { pipeline } from 'node:stream/promises' + +// Mock dependencies before any imports +vi.mock('execa', () => ({ + execa: vi.fn(), +})) + +vi.mock('extract-zip', () => ({ + default: vi.fn(), +})) + +vi.mock('fs-extra', () => ({ + default: { + mkdir: vi.fn(), + createWriteStream: vi.fn(() => new Writable()), + move: vi.fn(), + mkdtemp: vi.fn(async (prefix) => `${prefix}`), + rm: vi.fn(), + }, +})) + +vi.mock('got', () => ({ + default: { + stream: vi.fn(), + }, +})) + +vi.mock('node:os', () => ({ + platform: vi.fn(), + arch: vi.fn(), +})) + +vi.mock('path-exists', () => ({ + pathExists: vi.fn(), +})) + +vi.mock('node:stream/promises', () => ({ + pipeline: vi.fn(), +})) + +vi.mock('xdg-basedir', () => ({ + xdgCache: `./mocked/cache`, +})) + +describe('downloadRipGrep', () => { + beforeEach(() => { + vi.resetAllMocks() + // Mock got.stream to return a real, empty readable stream + const mockStream = new Readable({ + read() { + this.push(null) // Signal end of stream + }, + }) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + vi.mocked(got.stream).mockReturnValue(mockStream as any) + vi.mocked(pipeline).mockResolvedValue(undefined) + }) + + afterEach(() => { + vi.restoreAllMocks() + fs.rmSync('./test/bin', { recursive: true, force: true }) + fs.rmSync('./mocked', { recursive: true, force: true }) + }) + + const testMatrix = [ + { + platform: 'darwin', + arch: 'arm64', + target: 'aarch64-apple-darwin.tar.gz', + }, + { platform: 'darwin', arch: 'x64', target: 'x86_64-apple-darwin.tar.gz' }, + { platform: 'win32', arch: 'x64', target: 'x86_64-pc-windows-msvc.zip' }, + { platform: 'win32', arch: 'arm', target: 'aarch64-pc-windows-msvc.zip' }, + { + platform: 'linux', + arch: 'x64', + target: 'x86_64-unknown-linux-musl.tar.gz', + }, + { + platform: 'linux', + arch: 'arm64', + target: 'aarch64-unknown-linux-gnu.tar.gz', + }, + ] + + for (const { platform, arch, target } of testMatrix) { + it(`should download and extract for ${platform}-${arch}`, async () => { + vi.mocked(os.platform).mockReturnValue(platform as NodeJS.Platform) + vi.mocked(os.arch).mockReturnValue(arch) + vi.mocked(pathExists).mockResolvedValue(false) + + const binPath = './test/bin' + await downloadRipGrep(binPath) + + const version = 'v13.0.0-10' + const expectedUrl = `https://github.com/microsoft/ripgrep-prebuilt/releases/download/${version}/ripgrep-${version}-${target}` + const expectedDownloadPath = `./mocked/cache/vscode-ripgrep/ripgrep-${version}-${target}` + + // Check download + expect(got.stream).toHaveBeenCalledWith(expectedUrl) + expect(pipeline).toHaveBeenCalled() + + // Check extraction + if (target.endsWith('.tar.gz')) { + expect(execa).toHaveBeenCalledWith('tar', [ + 'xvf', + expectedDownloadPath, + '-C', + binPath, + ]) + expect(extractZip).not.toHaveBeenCalled() + } else if (target.endsWith('.zip')) { + expect(extractZip).toHaveBeenCalledWith(expectedDownloadPath, { + dir: binPath, + }) + expect(execa).not.toHaveBeenCalled() + } + }) + } + + it('should use the cached file if it exists', async () => { + vi.mocked(os.platform).mockReturnValue('linux') + vi.mocked(os.arch).mockReturnValue('x64') + vi.mocked(pathExists).mockResolvedValue(true) + + const binPath = './test/bin' + await downloadRipGrep(binPath) + + expect(got.stream).not.toHaveBeenCalled() + expect(pipeline).not.toHaveBeenCalled() + expect(execa).toHaveBeenCalled() // Still extracts + }) + + it('should throw an error for an unknown platform', async () => { + vi.mocked(os.platform).mockReturnValue('sunos' as NodeJS.Platform) // an unsupported platform + vi.mocked(os.arch).mockReturnValue('x64') + + await expect(downloadRipGrep('./test/bin')).rejects.toThrow( + 'Unknown platform: sunos', + ) + }) + + it('should clean up temporary files on successful download', async () => { + vi.mocked(os.platform).mockReturnValue('linux') + vi.mocked(os.arch).mockReturnValue('x64') + vi.mocked(pathExists).mockResolvedValue(false) + + await downloadRipGrep('./test/bin') + + expect(fsExtra.mkdtemp).toHaveBeenCalledWith('download-ripgrep') + expect(fsExtra.rm).toHaveBeenCalledWith('download-ripgrep', { + recursive: true, + force: true, + }) + }) +}) diff --git a/third_party/get-ripgrep/src/downloadRipGrep.js b/third_party/get-ripgrep/src/downloadRipGrep.ts similarity index 61% rename from third_party/get-ripgrep/src/downloadRipGrep.js rename to third_party/get-ripgrep/src/downloadRipGrep.ts index 906b2a9e2e..88ceecf2ff 100644 --- a/third_party/get-ripgrep/src/downloadRipGrep.js +++ b/third_party/get-ripgrep/src/downloadRipGrep.ts @@ -4,7 +4,6 @@ * Copyright 2023 Lvce Editor * SPDX-License-Identifier: MIT */ -import { VError } from '@lvce-editor/verror' import { execa } from 'execa' import extractZip from 'extract-zip' import fsExtra from 'fs-extra' @@ -13,22 +12,19 @@ import * as os from 'node:os' import { dirname, join } from 'node:path' import { pathExists } from 'path-exists' import { pipeline } from 'node:stream/promises' -import { temporaryFile } from 'tempy' import { fileURLToPath } from 'node:url' import { xdgCache } from 'xdg-basedir' - -const { mkdir, createWriteStream, move } = fsExtra +import path from 'path' const __dirname = dirname(fileURLToPath(import.meta.url)) const REPOSITORY = `microsoft/ripgrep-prebuilt` -const VERSION = process.env.RIPGREP_VERSION || 'v13.0.0-10' -console.log({ VERSION }) +const VERSION = process.env['RIPGREP_VERSION'] || 'v13.0.0-10' const BIN_PATH = join(__dirname, '../bin') const getTarget = () => { - const arch = process.env.npm_config_arch || os.arch() - const platform = process.env.platform || os.platform() + const arch = process.env['npm_config_arch'] || os.arch() + const platform = process.env['platform'] || os.platform() switch (platform) { case 'darwin': switch (arch) { @@ -63,61 +59,60 @@ const getTarget = () => { return 'i686-unknown-linux-musl.tar.gz' } default: - throw new VError('Unknown platform: ' + platform) + throw new Error('Unknown platform: ' + platform) } } -export const downloadFile = async (url, outFile) => { +async function downloadFile(url: string, outFile: string): Promise { + let tmpDir = undefined try { - const tmpFile = temporaryFile() - await pipeline(got.stream(url), createWriteStream(tmpFile)) - await mkdir(dirname(outFile), { recursive: true }) - await move(tmpFile, outFile) + tmpDir = await fsExtra.mkdtemp('download-ripgrep') + const tmpFile = path.join(tmpDir, 'tmp-file') + await pipeline(got.stream(url), fsExtra.createWriteStream(tmpFile)) + await fsExtra.mkdir(dirname(outFile), { recursive: true }) + await fsExtra.move(tmpFile, outFile) } catch (error) { - throw new VError(error, `Failed to download "${url}"`) + throw new Error(`Failed to download "${url}": ${error}`) + } finally { + if (tmpDir) { + await fsExtra.rm(tmpDir, { recursive: true, force: true }) + } } } -/** - * @param {string} inFile - * @param {string} outDir - */ -const unzip = async (inFile, outDir) => { +async function unzip(inFile: string, outDir: string): Promise { try { - await mkdir(outDir, { recursive: true }) + await fsExtra.mkdir(outDir, { recursive: true }) await extractZip(inFile, { dir: outDir }) } catch (error) { - throw new VError(error, `Failed to unzip "${inFile}"`) + throw new Error(`Failed to unzip "${inFile}": ${error}`) } } -/** - * @param {string} inFile - * @param {string} outDir - */ -const untarGz = async (inFile, outDir) => { +async function untarGz(inFile: string, outDir: string): Promise { try { - await mkdir(outDir, { recursive: true }) + await fsExtra.mkdir(outDir, { recursive: true }) await execa('tar', ['xvf', inFile, '-C', outDir]) } catch (error) { - throw new VError(error, `Failed to extract "${inFile}"`) + throw new Error(`Failed to extract "${inFile}": ${error}`) } } -export const downloadRipGrep = async () => { +export async function downloadRipGrep(overrideBinPath?: string): Promise { const target = getTarget() const url = `https://github.com/${REPOSITORY}/releases/download/${VERSION}/ripgrep-${VERSION}-${target}` const downloadPath = `${xdgCache}/vscode-ripgrep/ripgrep-${VERSION}-${target}` + const binPath = overrideBinPath ?? BIN_PATH if (!(await pathExists(downloadPath))) { await downloadFile(url, downloadPath) } else { console.info(`File ${downloadPath} has been cached`) } if (downloadPath.endsWith('.tar.gz')) { - await untarGz(downloadPath, BIN_PATH) + await untarGz(downloadPath, binPath) } else if (downloadPath.endsWith('.zip')) { - await unzip(downloadPath, BIN_PATH) + await unzip(downloadPath, binPath) } else { - throw new VError(`Invalid downloadPath ${downloadPath}`) + throw new Error(`Invalid downloadPath ${downloadPath}`) } } diff --git a/third_party/get-ripgrep/src/index.js b/third_party/get-ripgrep/src/index.js deleted file mode 100644 index 8fc965e98f..0000000000 --- a/third_party/get-ripgrep/src/index.js +++ /dev/null @@ -1,17 +0,0 @@ -/* eslint-disable */ -/** - * @license - * Copyright 2023 Lvce Editor - * SPDX-License-Identifier: MIT - */ -import { dirname, join } from 'node:path' -import { fileURLToPath } from 'node:url' - -const __dirname = dirname(fileURLToPath(import.meta.url)) - -export const rgPath = join( - __dirname, - '..', - 'bin', - `rg${process.platform === 'win32' ? '.exe' : ''}`, -) diff --git a/third_party/get-ripgrep/src/index.ts b/third_party/get-ripgrep/src/index.ts new file mode 100644 index 0000000000..f436ed49eb --- /dev/null +++ b/third_party/get-ripgrep/src/index.ts @@ -0,0 +1,7 @@ +/* eslint-disable */ +/** + * @license + * Copyright 2023 Lvce Editor + * SPDX-License-Identifier: MIT + */ +export { downloadRipGrep } from './downloadRipGrep.js' diff --git a/third_party/get-ripgrep/tsconfig.json b/third_party/get-ripgrep/tsconfig.json new file mode 100644 index 0000000000..8147e5a05b --- /dev/null +++ b/third_party/get-ripgrep/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "lib": ["DOM", "DOM.Iterable", "ES2021"], + "composite": true, + "types": ["node", "vitest/globals"], + "rootDir": "src", + "declaration": true + }, + "include": ["src/**/*.ts", "src/**/*.test.ts"], + "exclude": ["node_modules", "dist"] +}