diff --git a/src/helpers/dependency-files.test.ts b/src/helpers/dependency-files.test.ts new file mode 100644 index 0000000..922a8e9 --- /dev/null +++ b/src/helpers/dependency-files.test.ts @@ -0,0 +1,102 @@ +import { describe, it, expect, vi } from 'vitest'; +import { getDependencyFile, getDependencyFileName } from './dependency-files'; + +const mocks = vi.hoisted(() => { + return { + readFile: vi.fn(), + fileExists: vi.fn(), + }; +}); + +vi.mock('fs/promises', () => { + return { + readFile: mocks.readFile, + }; +}); + +vi.mock('./file-exists', () => { + return { + fileExists: mocks.fileExists, + }; +}); + +describe('getDependencyFile', () => { + it('should return the contents of package.json for node', async () => { + const packageJsonContent = JSON.stringify({ + name: 'example', + version: '1.0.0', + }); + mocks.readFile.mockResolvedValueOnce(packageJsonContent); + mocks.fileExists.mockResolvedValue(true); + + const result = await getDependencyFile('', 'ts'); + expect(result).toBe(packageJsonContent); + expect(mocks.readFile).toHaveBeenCalledWith('package.json', 'utf8'); + }); + + it('should return the contents of requirements.txt for python', async () => { + const requirementsTxtContent = 'example-package==1.0.0'; + mocks.readFile.mockResolvedValueOnce(requirementsTxtContent); + mocks.fileExists.mockResolvedValue(true); + + const result = await getDependencyFile('', 'py'); + expect(result).toBe(requirementsTxtContent); + expect(mocks.readFile).toHaveBeenCalledWith('requirements.txt', 'utf8'); + }); + + it('should return the contents of Gemfile for ruby', async () => { + const gemfileContent = "gem 'rails', '5.0.0'"; + mocks.readFile.mockResolvedValueOnce(gemfileContent); + mocks.fileExists.mockResolvedValue(true); + + const result = await getDependencyFile('', 'rb'); + expect(result).toBe(gemfileContent); + expect(mocks.readFile).toHaveBeenCalledWith('Gemfile', 'utf8'); + }); + + it('should check all three dependency files if no language is provided', async () => { + mocks.fileExists.mockReset(); + mocks.fileExists.mockResolvedValue(false); + + const result = await getDependencyFile('/src'); + expect(mocks.fileExists).toHaveBeenCalledTimes(3); + expect(mocks.fileExists).toHaveBeenCalledWith('/src/package.json'); + expect(mocks.fileExists).toHaveBeenCalledWith('/src/requirements.txt'); + expect(mocks.fileExists).toHaveBeenCalledWith('/src/Gemfile'); + }); + + it('should return null if package.json file does not exist', async () => { + mocks.fileExists.mockResolvedValue(false); + + await expect(getDependencyFile()).resolves.toBeNull(); + }); + + it('should return null if requirements.txt does not exist for python', async () => { + mocks.fileExists.mockResolvedValue(false); + + await expect(getDependencyFile(process.cwd(), 'py')).resolves.toBeNull(); + }); + + it('should return null if Gemfile does not exist for ruby', async () => { + mocks.fileExists.mockResolvedValue(false); + + await expect(getDependencyFile(process.cwd(), 'rb')).resolves.toBeNull(); + }); +}); + +describe('getDependencyFileName', () => { + it('should return package.json for node', () => { + const result = getDependencyFileName(); + expect(result).toBe('package.json'); + }); + + it('should return requirements.txt for python', () => { + const result = getDependencyFileName('py'); + expect(result).toBe('requirements.txt'); + }); + + it('should return Gemfile for ruby', () => { + const result = getDependencyFileName('rb'); + expect(result).toBe('Gemfile'); + }); +}); diff --git a/src/helpers/dependency-files.ts b/src/helpers/dependency-files.ts new file mode 100644 index 0000000..ad825bf --- /dev/null +++ b/src/helpers/dependency-files.ts @@ -0,0 +1,68 @@ +import * as fs from 'fs/promises'; +import * as path from 'path'; +import { fileExists } from './file-exists'; + +/** + * Find dependency file in the given directory or any + * parent directory. Returns the content of the dependency file. + */ +export async function getDependencyFile( + directory = process.cwd(), + language?: string +): Promise { + let currentDirectory = directory; + while (currentDirectory !== '/') { + if (language) { + const filePath = getDependenciesFilePath(directory, language); + + if (await fileExists(filePath)) { + return getDependenciesFileContent(directory, language); + } + } else { + let filePath = getDependenciesFilePath(directory, 'py'); + if (await fileExists(filePath)) { + return getDependenciesFileContent(directory, 'py'); + } + filePath = getDependenciesFilePath(directory, 'rb'); + if (await fileExists(filePath)) { + return getDependenciesFileContent(directory, 'rb'); + } + filePath = getDependenciesFilePath(directory, 'js'); + if (await fileExists(filePath)) { + return getDependenciesFileContent(directory, 'js'); + } + } + currentDirectory = path.dirname(currentDirectory); + } + return null; +} + +export function getDependencyFileName(language?: string): string { + let fileName; + switch (language) { + case 'py': + fileName = 'requirements.txt'; + break; + case 'rb': + fileName = 'Gemfile'; + break; + default: + fileName = 'package.json'; + break; + } + return fileName; +} + +function getDependenciesFilePath(directory: string, language?: string): string { + const fileName = getDependencyFileName(language); + return path.join(directory, fileName); +} + +async function getDependenciesFileContent( + directory = process.cwd(), + language?: string +): Promise { + const filePath = getDependenciesFilePath(directory, language); + const fileContent = await fs.readFile(filePath, 'utf8'); + return fileContent; +} diff --git a/src/helpers/find-package-json.ts b/src/helpers/find-package-json.ts index 66525e7..e69de29 100644 --- a/src/helpers/find-package-json.ts +++ b/src/helpers/find-package-json.ts @@ -1,21 +0,0 @@ -import * as fs from 'fs/promises'; -import * as path from 'path'; -import { fileExists } from './file-exists'; - -/** - * Find package.json file in the given directory or any - * parent directory. - */ -export async function findPackageJson( - directory = process.cwd() -): Promise { - let currentDirectory = directory; - while (currentDirectory !== '/') { - const packageJsonPath = path.join(currentDirectory, 'package.json'); - if (await fileExists(packageJsonPath)) { - return fs.readFile(packageJsonPath, 'utf-8'); - } - currentDirectory = path.dirname(currentDirectory); - } - return null; -} diff --git a/src/helpers/get-test-command.ts b/src/helpers/get-test-command.ts index f9afe40..d092f0b 100644 --- a/src/helpers/get-test-command.ts +++ b/src/helpers/get-test-command.ts @@ -1,5 +1,5 @@ import dedent from 'dedent'; -import { findPackageJson } from './find-package-json'; +import { getDependencyFile, getDependencyFileName } from './dependency-files'; import { getSimpleCompletion } from './llm'; import { removeBackticks } from './remove-backticks'; @@ -12,8 +12,13 @@ export async function getTestCommand({ testFilePath.split('/').pop()!.split('.')[0] }`; - const packageJson = await findPackageJson(); - if (!packageJson) { + const testFileExtension = testFilePath.split('.').pop(); + const dependencyFileName = getDependencyFileName(testFileExtension); + const dependencyFileContent = await getDependencyFile( + process.cwd(), + testFileExtension + ); + if (!dependencyFileContent) { return defaultTestCommand; } @@ -28,23 +33,31 @@ export async function getTestCommand({ { role: 'user', content: dedent` - Here is my package.json. I want to run a single command to execute the tests. The tests should not run in watch mode. - If there is a test script in the package.json, use that script. For example, \`npm test\`. + Here is my ${dependencyFileName}. I want to run a single command to execute the tests. The tests should not run in watch mode. + If there is a test script in the ${dependencyFileName}, use that script. For example, \`npm test\`. The command should filter and run the specific test file at \`${testFilePath}\`. For example, \`npm test -- ${ testFilePath.split('/').pop()!.split('.')[0] }\`. - Here are sample npm test commands without watch mode that work for some popular testing libraries: + Here are sample test commands without watch mode that work for some popular testing libraries: - Jest: \`npm test -- ${ testFilePath.split('/').pop()!.split('.')[0] } --no-watch \` - Vitest: \`npm test -- ${ testFilePath.split('/').pop()!.split('.')[0] } --run \` + - minitest: \`rails test ${ + testFilePath.split('/').pop()!.split('.')[0] + }\` + - rspec: \`rspec ${testFilePath.split('/').pop()!.split('.')[0]}\` + - pytest: \`pytest ${testFilePath.split('/').pop()!.split('.')[0]}\` + - unittest: \`python -m unittest ${ + testFilePath.split('/').pop()!.split('.')[0] + }\` - - ${packageJson} - + <${dependencyFileName.replace('.', '-')}> + ${dependencyFileContent} + If no testing libraries are found in the package.json, use \`npx vitest run ${ testFilePath.split('/').pop()!.split('.')[0] diff --git a/src/helpers/validate-project.ts b/src/helpers/validate-project.ts index e8a6f75..6aa2b3a 100644 --- a/src/helpers/validate-project.ts +++ b/src/helpers/validate-project.ts @@ -1,24 +1,6 @@ -import fs from 'fs/promises'; -import path from 'path'; -import { findPackageJson } from './find-package-json'; +import { getDependencyFile } from './dependency-files'; export async function isValidProject(): Promise { - const currentDir = process.cwd(); - const requirementsTxtPath = path.join(currentDir, 'requirements.txt'); - - try { - await findPackageJson(); - return true; - } catch { - // Do nothing if file doesn't exist - } - - try { - await fs.access(requirementsTxtPath); - return true; - } catch { - // Do nothing if file doesn't exist - } - - return false; + const fileContent = await getDependencyFile(); + return fileContent !== null; }