ai-test-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/commands/init.ts","../../src/core/config.ts","../../src/core/logger.ts","../../src/core/detector.ts","../../src/core/setup.ts","../../src/core/ai.ts","../../src/core/config-fixer.ts"],"sourcesContent":["import { Command } from 'commander';\nimport prompts from 'prompts';\nimport { existsSync, appendFileSync, readFileSync } from 'fs';\nimport { resolve } from 'path';\nimport { saveConfig } from '../core/config.js';\nimport { detectProjectInfo } from '../core/detector.js';\nimport { logger } from '../core/logger.js';\nimport { installTestRunner } from '../core/setup.js';\nimport { askAI } from '../core/ai.js';\nimport { applyConfigFix } from '../core/config-fixer.js';\nimport fg from 'fast-glob';\n\nexport const initCommand = new Command('init')\n .description('Initialize the project')\n .action(async () => {\n logger.info('Initializing AI Test CLI...');\n \n const projectInfo = detectProjectInfo();\n logger.info(`Detected Language: ${projectInfo.language}`);\n logger.info(`Detected Framework: ${projectInfo.framework}`);\n logger.info(`Detected Test Runner: ${projectInfo.testRunner}`);\n logger.info(`Detected Package Manager: ${projectInfo.packageManager}`);\n\n if (projectInfo.testRunner === 'unknown') {\n const setupRes = await prompts({\n type: 'confirm',\n name: 'installRunner',\n message: 'No test runner detected. Would you like AI Test CLI to automatically install and configure one?',\n initial: true\n });\n\n if (setupRes.installRunner) {\n await installTestRunner(projectInfo);\n }\n }\n\n const response = await prompts([\n {\n type: 'select',\n name: 'provider',\n message: 'Select AI Provider',\n choices: [\n { title: 'OpenAI', value: 'openai' },\n { title: 'Anthropic', value: 'anthropic' },\n { title: 'DeepSeek', value: 'deepseek' },\n { title: 'Gemini', value: 'gemini' },\n { title: 'Ollama (Local)', value: 'ollama' },\n { title: 'Custom (OpenAI Compatible)', value: 'custom' },\n ],\n initial: 0\n },\n {\n type: 'text',\n name: 'model',\n message: 'Enter model name',\n initial: (prev: string) => {\n if (prev === 'openai') return 'gpt-4o';\n if (prev === 'anthropic') return 'claude-3-5-sonnet-latest';\n if (prev === 'deepseek') return 'deepseek-coder';\n if (prev === 'gemini') return 'gemini-2.5-flash';\n if (prev === 'ollama') return 'llama3.1';\n if (prev === 'custom') return 'local-model';\n return '';\n }\n },\n {\n type: (prev, values) => values.provider === 'custom' ? 'text' : null,\n name: 'baseURL',\n message: 'Enter API Base URL (e.g., http://localhost:1234/v1)'\n },\n {\n type: (prev, values) => values.provider === 'custom' ? 'text' : null,\n name: 'customHeaders',\n message: 'Enter custom headers as \"Key: Value, Key2: Value2\" (or leave empty)'\n },\n {\n type: (prev, values) => values.provider === 'ollama' ? null : 'password',\n name: 'apiKey',\n message: 'Enter API Key'\n }\n ]);\n\n if (!response.provider || !response.model) {\n logger.error('Initialization aborted.');\n return;\n }\n\n if (response.apiKey) {\n const envKeyMap: Record<string, string> = {\n openai: 'OPENAI_API_KEY',\n anthropic: 'ANTHROPIC_API_KEY',\n deepseek: 'DEEPSEEK_API_KEY',\n gemini: 'GEMINI_API_KEY',\n custom: 'CUSTOM_API_KEY'\n };\n const envKey = envKeyMap[response.provider] || 'CUSTOM_API_KEY';\n \n const envPath = resolve(process.cwd(), '.env');\n appendFileSync(envPath, `\\n${envKey}=${response.apiKey}\\n`, 'utf-8');\n process.env[envKey] = response.apiKey; // Inject into current runtime\n logger.success(`Saved API key to .env securely.`);\n\n const gitignorePath = resolve(process.cwd(), '.gitignore');\n let gitignoreContent = '';\n if (existsSync(gitignorePath)) {\n gitignoreContent = readFileSync(gitignorePath, 'utf-8');\n }\n if (!gitignoreContent.includes('.env')) {\n appendFileSync(gitignorePath, '\\n.env\\n', 'utf-8');\n logger.info('Added .env to .gitignore');\n }\n }\n\n let parsedHeaders: Record<string, string> | undefined = undefined;\n if (response.customHeaders && response.customHeaders.trim() !== '') {\n parsedHeaders = {};\n const parts = response.customHeaders.split(',');\n for (const part of parts) {\n const [key, ...valParts] = part.split(':');\n if (key && valParts.length > 0) {\n const cleanKey = key.trim().replace(/^[\"']|[\"']$/g, '');\n const cleanVal = valParts.join(':').trim().replace(/^[\"']|[\"']$/g, '');\n parsedHeaders[cleanKey] = cleanVal;\n }\n }\n }\n\n const config = {\n provider: response.provider,\n model: response.model,\n baseURL: response.baseURL,\n customHeaders: parsedHeaders,\n temperature: 0.1,\n autoFix: true,\n maxRetries: -1 // Infinite by default\n };\n\n await saveConfig(config);\n logger.success('Basic initialization complete.');\n\n const setupVerify = await prompts({\n type: 'confirm',\n name: 'verify',\n message: 'Would you like the AI to verify and configure your testing environment right now? (Recommended)',\n initial: true\n });\n\n if (setupVerify.verify) {\n const spinner = logger.spinner('Analyzing project setup...').start();\n try {\n const pkgPath = resolve(process.cwd(), 'package.json');\n let pkgContent = '{}';\n if (existsSync(pkgPath)) {\n pkgContent = readFileSync(pkgPath, 'utf-8');\n }\n \n const existingConfigs = await fg(['jest.config.*', 'vite.config.*', '.babelrc*', 'tsconfig.*', 'vitest.config.*'], { cwd: process.cwd(), deep: 1 });\n const setupContext = `Language: ${projectInfo.language}\\nFramework: ${projectInfo.framework}\\nTest Runner: ${projectInfo.testRunner}\\nExisting Config Files: ${existingConfigs.join(', ') || 'None'}\\nPackage.json:\\n${pkgContent}`;\n \n const responseCode = await askAI(\n config,\n 'You are a testing architecture expert. Analyze this project setup. If the test environment is perfect and ready to run tests, reply exactly with \"READY\". If it is missing dependencies (e.g., Jest, Vitest, Testing Library, jsdom) or needs config files (e.g., .babelrc, jest.config.js) to support the detected framework, reply ONLY with a JSON block wrapped in ```json starting with {\"type\": \"CONFIG_FIX\", \"dependencies\": [\"pkg\"], \"files\": [{\"path\": \".babelrc\", \"content\": \"...\"}], \"reason\": \"...\"}. CRITICAL: You must escape all newlines within the JSON \"content\" strings as \\\\n. DO NOT create duplicate configuration files for the same tool (e.g., if jest.config.js exists, do not create jest.config.ts). If an existing file needs updating, provide the file path of the existing file. Do not output raw unescaped newlines inside the JSON string. Do not output anything else.',\n setupContext\n );\n\n const configFixMatch = responseCode.match(/```json\\s*(\\{[\\s\\S]*?\"type\":\\s*\"CONFIG_FIX\"[\\s\\S]*?\\})\\s*```/);\n if (configFixMatch && configFixMatch[1]) {\n const configFix = JSON.parse(configFixMatch[1]);\n await applyConfigFix(configFix, projectInfo, spinner);\n spinner.succeed('Test environment successfully scaffolded!');\n } else {\n spinner.succeed('Test environment looks perfectly configured!');\n }\n } catch (e: any) {\n spinner.fail(`Setup verification failed: ${e.message}`);\n }\n }\n\n logger.success('You can now use `aitest run` or `aitest generate`.');\n });\n","import { cosmiconfig } from 'cosmiconfig';\nimport { writeFile } from 'fs/promises';\nimport { resolve } from 'path';\nimport { logger } from './logger.js';\n\nexport interface AITestConfig {\n provider: string;\n model: string;\n apiKey?: string;\n baseURL?: string;\n customHeaders?: Record<string, string>;\n temperature?: number;\n autoFix?: boolean;\n maxRetries?: number;\n}\n\nconst moduleName = 'aitest';\nconst explorer = cosmiconfig(moduleName);\n\nexport async function loadConfig(): Promise<AITestConfig | null> {\n try {\n const result = await explorer.search();\n if (result) {\n return result.config as AITestConfig;\n }\n return null;\n } catch (error) {\n logger.error(`Failed to load config: ${error}`);\n return null;\n }\n}\n\nexport async function saveConfig(config: AITestConfig, targetDir: string = process.cwd()): Promise<void> {\n const configPath = resolve(targetDir, '.aitestrc.json');\n try {\n await writeFile(configPath, JSON.stringify(config, null, 2), 'utf-8');\n logger.success(`Saved config to ${configPath}`);\n } catch (error) {\n logger.error(`Failed to save config: ${error}`);\n throw error;\n }\n}\n","import chalk from 'chalk';\nimport ora from 'ora';\n\nexport const logger = {\n info: (msg: string) => console.log(chalk.blue('ℹ'), msg),\n success: (msg: string) => console.log(chalk.green('✔'), msg),\n error: (msg: string) => console.error(chalk.red('✖'), msg),\n warn: (msg: string) => console.warn(chalk.yellow('⚠'), msg),\n log: (msg: string) => console.log(msg),\n spinner: (text: string) => ora(text),\n};\n","import { readFileSync, existsSync } from 'fs';\nimport { resolve } from 'path';\n\nexport interface ProjectInfo {\n language: 'javascript' | 'typescript';\n testRunner: 'jest' | 'vitest' | 'mocha' | 'unknown';\n framework: 'react' | 'vue' | 'angular' | 'node' | 'unknown';\n packageManager: 'npm' | 'yarn' | 'pnpm' | 'bun';\n}\n\nexport function detectProjectInfo(targetDir: string = process.cwd()): ProjectInfo {\n const packageJsonPath = resolve(targetDir, 'package.json');\n const tsconfigPath = resolve(targetDir, 'tsconfig.json');\n \n let packageJson: any = {};\n if (existsSync(packageJsonPath)) {\n try {\n packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\n } catch (e) {\n // ignore\n }\n }\n\n const allDeps = {\n ...(packageJson.dependencies || {}),\n ...(packageJson.devDependencies || {})\n };\n\n const language = existsSync(tsconfigPath) || allDeps['typescript'] ? 'typescript' : 'javascript';\n \n let testRunner: ProjectInfo['testRunner'] = 'unknown';\n if (allDeps['jest']) testRunner = 'jest';\n else if (allDeps['vitest']) testRunner = 'vitest';\n else if (allDeps['mocha']) testRunner = 'mocha';\n\n let framework: ProjectInfo['framework'] = 'unknown';\n if (allDeps['react']) framework = 'react';\n else if (allDeps['vue']) framework = 'vue';\n else if (allDeps['@angular/core']) framework = 'angular';\n else if (allDeps['express'] || allDeps['@nestjs/core'] || allDeps['fastify']) framework = 'node';\n\n let packageManager: ProjectInfo['packageManager'] = 'npm';\n if (existsSync(resolve(targetDir, 'pnpm-lock.yaml'))) packageManager = 'pnpm';\n else if (existsSync(resolve(targetDir, 'yarn.lock'))) packageManager = 'yarn';\n else if (existsSync(resolve(targetDir, 'bun.lockb'))) packageManager = 'bun';\n\n return { language, testRunner, framework, packageManager };\n}\n","import { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { resolve } from 'path';\nimport { readFileSync, writeFileSync } from 'fs';\nimport { ProjectInfo } from './detector.js';\nimport { logger } from './logger.js';\n\nconst execAsync = promisify(exec);\n\nexport async function installTestRunner(projectInfo: ProjectInfo, targetDir: string = process.cwd()) {\n const spinner = logger.spinner('Setting up test runner...').start();\n \n const isTs = projectInfo.language === 'typescript';\n const runner = isTs ? 'vitest' : 'jest';\n const packages = isTs ? 'vitest' : 'jest';\n \n let installCmd = '';\n switch (projectInfo.packageManager) {\n case 'npm':\n installCmd = `npm install -D ${packages}`;\n break;\n case 'yarn':\n installCmd = `yarn add -D ${packages}`;\n break;\n case 'pnpm':\n installCmd = `pnpm add -D ${packages}`;\n break;\n case 'bun':\n installCmd = `bun add -D ${packages}`;\n break;\n }\n\n try {\n spinner.text = `Installing ${runner} using ${projectInfo.packageManager}...`;\n await execAsync(installCmd, { cwd: targetDir });\n \n spinner.text = 'Updating package.json...';\n const packageJsonPath = resolve(targetDir, 'package.json');\n let pkg: any = {};\n try {\n pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\n } catch (e) {\n pkg = {};\n }\n\n if (!pkg.scripts) pkg.scripts = {};\n \n if (runner === 'vitest') {\n pkg.scripts.test = 'vitest run';\n } else {\n pkg.scripts.test = 'jest';\n }\n\n writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2), 'utf-8');\n \n spinner.succeed(`Successfully installed and configured ${runner}!`);\n } catch (error: any) {\n spinner.fail(`Failed to set up test runner: ${error.message}`);\n }\n}\n","import { generateText } from 'ai';\nimport { createOpenAI } from '@ai-sdk/openai';\nimport { createAnthropic } from '@ai-sdk/anthropic';\nimport { createGoogleGenerativeAI } from '@ai-sdk/google';\nimport { createDeepSeek } from '@ai-sdk/deepseek';\nimport { AITestConfig } from './config.js';\nimport * as dotenv from 'dotenv';\n\ndotenv.config();\n\nexport function getAIModel(config: AITestConfig) {\n const providerName = config.provider.toLowerCase();\n \n if (providerName === 'openai') {\n const openai = createOpenAI({\n apiKey: config.apiKey || process.env.OPENAI_API_KEY,\n });\n return openai(config.model || 'gpt-4o');\n } \n \n if (providerName === 'anthropic') {\n const anthropic = createAnthropic({\n apiKey: config.apiKey || process.env.ANTHROPIC_API_KEY,\n });\n return anthropic(config.model || 'claude-3-5-sonnet-20240620');\n }\n\n if (providerName === 'gemini' || providerName === 'google') {\n const google = createGoogleGenerativeAI({\n apiKey: config.apiKey || process.env.GEMINI_API_KEY || process.env.GOOGLE_GENERATIVE_AI_API_KEY,\n });\n return google(config.model || 'gemini-2.5-flash');\n }\n\n if (providerName === 'deepseek') {\n const deepseek = createDeepSeek({\n apiKey: config.apiKey || process.env.DEEPSEEK_API_KEY,\n });\n return deepseek(config.model || 'deepseek-coder');\n }\n\n if (providerName === 'ollama') {\n const ollama = createOpenAI({\n baseURL: 'http://localhost:11434/v1',\n apiKey: 'ollama', // API key isn't strictly needed for local Ollama\n });\n return ollama(config.model || 'llama3.1');\n }\n\n if (providerName === 'custom') {\n const custom = createOpenAI({\n baseURL: config.baseURL,\n apiKey: config.apiKey || process.env.CUSTOM_API_KEY || 'custom',\n headers: config.customHeaders,\n });\n return custom(config.model || 'local-model');\n }\n\n throw new Error(`Unsupported AI provider: ${config.provider}`);\n}\n\nexport async function askAI(config: AITestConfig, system: string, prompt: string) {\n const model = getAIModel(config);\n \n const { text } = await generateText({\n model,\n system,\n prompt,\n maxRetries: 5,\n temperature: config.temperature || 0.1,\n });\n\n return text;\n}\n","import { writeFileSync, readFileSync } from 'fs';\nimport { resolve } from 'path';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { logger } from './logger.js';\nimport { ProjectInfo } from './detector.js';\n\nconst execAsync = promisify(exec);\n\nexport interface ConfigFixPayload {\n type: string;\n dependencies?: string[];\n files?: { path: string; content: string }[];\n reason: string;\n}\n\nexport async function applyConfigFix(configFix: ConfigFixPayload, projectInfo: ProjectInfo, spinner: any = logger.spinner('Applying fix...')) {\n spinner.info(`Autonomous Config Fix initiated: ${configFix.reason}`);\n \n if (configFix.dependencies && configFix.dependencies.length > 0) {\n spinner.text = `Installing missing dependencies: ${configFix.dependencies.join(', ')}...`;\n const installCmd = projectInfo.packageManager === 'yarn' ? 'yarn add -D' : \n projectInfo.packageManager === 'pnpm' ? 'pnpm add -D' : \n projectInfo.packageManager === 'bun' ? 'bun add -d' : 'npm install -D --legacy-peer-deps';\n await execAsync(`${installCmd} ${configFix.dependencies.join(' ')}`, { cwd: process.cwd() });\n }\n \n if (configFix.files && configFix.files.length > 0) {\n for (const file of configFix.files) {\n const filePath = resolve(process.cwd(), file.path);\n spinner.text = `Writing config file: ${file.path}...`;\n \n if (file.path.endsWith('package.json')) {\n try {\n const existingPkg = JSON.parse(readFileSync(filePath, 'utf-8'));\n const newPkg = JSON.parse(file.content);\n \n if (newPkg.scripts) {\n existingPkg.scripts = { ...(existingPkg.scripts || {}), ...newPkg.scripts };\n }\n if (newPkg.jest) {\n existingPkg.jest = { ...(existingPkg.jest || {}), ...newPkg.jest };\n }\n \n writeFileSync(filePath, JSON.stringify(existingPkg, null, 2), 'utf-8');\n } catch (e: any) {\n spinner.warn(`Failed to safely merge package.json: ${e.message}`);\n }\n } else {\n writeFileSync(filePath, file.content, 'utf-8');\n }\n }\n }\n}\n"],"mappings":"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","names":["existsSync","readFileSync","resolve","config","resolve","resolve","readFileSync","resolve","readFileSync","config","writeFileSync","readFileSync","resolve","exec","promisify","execAsync","promisify","exec","resolve","readFileSync","writeFileSync","resolve","existsSync","readFileSync","config"]}
@@ -0,0 +1,5 @@
1
+ import { Command } from 'commander';
2
+
3
+ declare const runCommand: Command;
4
+
5
+ export { runCommand };
@@ -0,0 +1,190 @@
1
+ // src/commands/run.ts
2
+ import { Command } from "commander";
3
+
4
+ // src/core/config.ts
5
+ import { cosmiconfig } from "cosmiconfig";
6
+
7
+ // src/core/logger.ts
8
+ import chalk from "chalk";
9
+ import ora from "ora";
10
+ var logger = {
11
+ info: (msg) => console.log(chalk.blue("\u2139"), msg),
12
+ success: (msg) => console.log(chalk.green("\u2714"), msg),
13
+ error: (msg) => console.error(chalk.red("\u2716"), msg),
14
+ warn: (msg) => console.warn(chalk.yellow("\u26A0"), msg),
15
+ log: (msg) => console.log(msg),
16
+ spinner: (text) => ora(text)
17
+ };
18
+
19
+ // src/core/config.ts
20
+ var moduleName = "aitest";
21
+ var explorer = cosmiconfig(moduleName);
22
+ async function loadConfig() {
23
+ try {
24
+ const result = await explorer.search();
25
+ if (result) {
26
+ return result.config;
27
+ }
28
+ return null;
29
+ } catch (error) {
30
+ logger.error(`Failed to load config: ${error}`);
31
+ return null;
32
+ }
33
+ }
34
+
35
+ // src/core/ai.ts
36
+ import { generateText } from "ai";
37
+ import { createOpenAI } from "@ai-sdk/openai";
38
+ import { createAnthropic } from "@ai-sdk/anthropic";
39
+ import { createGoogleGenerativeAI } from "@ai-sdk/google";
40
+ import { createDeepSeek } from "@ai-sdk/deepseek";
41
+ import * as dotenv from "dotenv";
42
+ dotenv.config();
43
+ function getAIModel(config2) {
44
+ const providerName = config2.provider.toLowerCase();
45
+ if (providerName === "openai") {
46
+ const openai = createOpenAI({
47
+ apiKey: config2.apiKey || process.env.OPENAI_API_KEY
48
+ });
49
+ return openai(config2.model || "gpt-4o");
50
+ }
51
+ if (providerName === "anthropic") {
52
+ const anthropic = createAnthropic({
53
+ apiKey: config2.apiKey || process.env.ANTHROPIC_API_KEY
54
+ });
55
+ return anthropic(config2.model || "claude-3-5-sonnet-20240620");
56
+ }
57
+ if (providerName === "gemini" || providerName === "google") {
58
+ const google = createGoogleGenerativeAI({
59
+ apiKey: config2.apiKey || process.env.GEMINI_API_KEY || process.env.GOOGLE_GENERATIVE_AI_API_KEY
60
+ });
61
+ return google(config2.model || "gemini-2.5-flash");
62
+ }
63
+ if (providerName === "deepseek") {
64
+ const deepseek = createDeepSeek({
65
+ apiKey: config2.apiKey || process.env.DEEPSEEK_API_KEY
66
+ });
67
+ return deepseek(config2.model || "deepseek-coder");
68
+ }
69
+ if (providerName === "ollama") {
70
+ const ollama = createOpenAI({
71
+ baseURL: "http://localhost:11434/v1",
72
+ apiKey: "ollama"
73
+ // API key isn't strictly needed for local Ollama
74
+ });
75
+ return ollama(config2.model || "llama3.1");
76
+ }
77
+ if (providerName === "custom") {
78
+ const custom = createOpenAI({
79
+ baseURL: config2.baseURL,
80
+ apiKey: config2.apiKey || process.env.CUSTOM_API_KEY || "custom",
81
+ headers: config2.customHeaders
82
+ });
83
+ return custom(config2.model || "local-model");
84
+ }
85
+ throw new Error(`Unsupported AI provider: ${config2.provider}`);
86
+ }
87
+ async function askAI(config2, system, prompt) {
88
+ const model = getAIModel(config2);
89
+ const { text } = await generateText({
90
+ model,
91
+ system,
92
+ prompt,
93
+ maxRetries: 5,
94
+ temperature: config2.temperature || 0.1
95
+ });
96
+ return text;
97
+ }
98
+
99
+ // src/commands/run.ts
100
+ import { exec } from "child_process";
101
+ import { promisify } from "util";
102
+
103
+ // src/core/detector.ts
104
+ import { readFileSync, existsSync } from "fs";
105
+ import { resolve } from "path";
106
+ function detectProjectInfo(targetDir = process.cwd()) {
107
+ const packageJsonPath = resolve(targetDir, "package.json");
108
+ const tsconfigPath = resolve(targetDir, "tsconfig.json");
109
+ let packageJson = {};
110
+ if (existsSync(packageJsonPath)) {
111
+ try {
112
+ packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
113
+ } catch (e) {
114
+ }
115
+ }
116
+ const allDeps = {
117
+ ...packageJson.dependencies || {},
118
+ ...packageJson.devDependencies || {}
119
+ };
120
+ const language = existsSync(tsconfigPath) || allDeps["typescript"] ? "typescript" : "javascript";
121
+ let testRunner = "unknown";
122
+ if (allDeps["jest"]) testRunner = "jest";
123
+ else if (allDeps["vitest"]) testRunner = "vitest";
124
+ else if (allDeps["mocha"]) testRunner = "mocha";
125
+ let framework = "unknown";
126
+ if (allDeps["react"]) framework = "react";
127
+ else if (allDeps["vue"]) framework = "vue";
128
+ else if (allDeps["@angular/core"]) framework = "angular";
129
+ else if (allDeps["express"] || allDeps["@nestjs/core"] || allDeps["fastify"]) framework = "node";
130
+ let packageManager = "npm";
131
+ if (existsSync(resolve(targetDir, "pnpm-lock.yaml"))) packageManager = "pnpm";
132
+ else if (existsSync(resolve(targetDir, "yarn.lock"))) packageManager = "yarn";
133
+ else if (existsSync(resolve(targetDir, "bun.lockb"))) packageManager = "bun";
134
+ return { language, testRunner, framework, packageManager };
135
+ }
136
+
137
+ // src/commands/run.ts
138
+ var execAsync = promisify(exec);
139
+ var runCommand = new Command("run").description("Run tests and analyze with AI (implicitly tracks coverage)").action(async () => {
140
+ const config2 = await loadConfig();
141
+ if (!config2) {
142
+ logger.error("Configuration not found. Run `aitest init` first.");
143
+ return;
144
+ }
145
+ const projectInfo = detectProjectInfo();
146
+ let testCommand = "npm test -- --coverage";
147
+ if (projectInfo.testRunner === "mocha" || projectInfo.testRunner === "unknown") {
148
+ testCommand = "npm test --coverage";
149
+ }
150
+ const spinner = logger.spinner(`Running tests with coverage (${testCommand})...`).start();
151
+ let stdoutStr = "";
152
+ let stderrStr = "";
153
+ let testFailed = false;
154
+ try {
155
+ const { stdout, stderr } = await execAsync(testCommand);
156
+ stdoutStr = stdout;
157
+ stderrStr = stderr;
158
+ spinner.succeed("Tests passed!");
159
+ } catch (error) {
160
+ testFailed = true;
161
+ stdoutStr = error.stdout || "";
162
+ stderrStr = error.stderr || error.message;
163
+ spinner.fail("Tests failed.");
164
+ }
165
+ const outputToAnalyze = `
166
+ STDOUT:
167
+ ${stdoutStr.slice(-2e3)}
168
+
169
+ STDERR:
170
+ ${stderrStr.slice(-2e3)}
171
+ `.trim();
172
+ logger.info("Analyzing test results with AI...");
173
+ const analyzeSpinner = logger.spinner("Thinking...").start();
174
+ try {
175
+ const analysis = await askAI(
176
+ config2,
177
+ "You are an expert QA and software testing engineer. Analyze the provided test output and code coverage metrics. Keep it concise. If there are failures, provide a clear summary of what failed and why. Also, briefly analyze the coverage table and suggest what areas of the code need more tests.",
178
+ outputToAnalyze
179
+ );
180
+ analyzeSpinner.succeed("Analysis complete");
181
+ console.log("\n" + analysis + "\n");
182
+ } catch (error) {
183
+ analyzeSpinner.fail("AI analysis failed.");
184
+ logger.error(error.message);
185
+ }
186
+ });
187
+ export {
188
+ runCommand
189
+ };
190
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/commands/run.ts","../../src/core/config.ts","../../src/core/logger.ts","../../src/core/ai.ts","../../src/core/detector.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { loadConfig } from '../core/config.js';\nimport { logger } from '../core/logger.js';\nimport { askAI } from '../core/ai.js';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\n\nconst execAsync = promisify(exec);\n\nimport { detectProjectInfo } from '../core/detector.js';\n\nexport const runCommand = new Command('run')\n .description('Run tests and analyze with AI (implicitly tracks coverage)')\n .action(async () => {\n const config = await loadConfig();\n if (!config) {\n logger.error('Configuration not found. Run `aitest init` first.');\n return;\n }\n\n const projectInfo = detectProjectInfo();\n let testCommand = 'npm test -- --coverage';\n if (projectInfo.testRunner === 'mocha' || projectInfo.testRunner === 'unknown') {\n testCommand = 'npm test --coverage';\n }\n\n const spinner = logger.spinner(`Running tests with coverage (${testCommand})...`).start();\n \n let stdoutStr = '';\n let stderrStr = '';\n let testFailed = false;\n\n try {\n const { stdout, stderr } = await execAsync(testCommand);\n stdoutStr = stdout;\n stderrStr = stderr;\n spinner.succeed('Tests passed!');\n } catch (error: any) {\n testFailed = true;\n stdoutStr = error.stdout || '';\n stderrStr = error.stderr || error.message;\n spinner.fail('Tests failed.');\n }\n\n const outputToAnalyze = `\nSTDOUT:\n${stdoutStr.slice(-2000)}\n\nSTDERR:\n${stderrStr.slice(-2000)}\n `.trim();\n\n logger.info('Analyzing test results with AI...');\n const analyzeSpinner = logger.spinner('Thinking...').start();\n \n try {\n const analysis = await askAI(\n config,\n 'You are an expert QA and software testing engineer. Analyze the provided test output and code coverage metrics. Keep it concise. If there are failures, provide a clear summary of what failed and why. Also, briefly analyze the coverage table and suggest what areas of the code need more tests.',\n outputToAnalyze\n );\n \n analyzeSpinner.succeed('Analysis complete');\n console.log('\\n' + analysis + '\\n');\n \n } catch (error: any) {\n analyzeSpinner.fail('AI analysis failed.');\n logger.error(error.message);\n }\n });\n","import { cosmiconfig } from 'cosmiconfig';\nimport { writeFile } from 'fs/promises';\nimport { resolve } from 'path';\nimport { logger } from './logger.js';\n\nexport interface AITestConfig {\n provider: string;\n model: string;\n apiKey?: string;\n baseURL?: string;\n customHeaders?: Record<string, string>;\n temperature?: number;\n autoFix?: boolean;\n maxRetries?: number;\n}\n\nconst moduleName = 'aitest';\nconst explorer = cosmiconfig(moduleName);\n\nexport async function loadConfig(): Promise<AITestConfig | null> {\n try {\n const result = await explorer.search();\n if (result) {\n return result.config as AITestConfig;\n }\n return null;\n } catch (error) {\n logger.error(`Failed to load config: ${error}`);\n return null;\n }\n}\n\nexport async function saveConfig(config: AITestConfig, targetDir: string = process.cwd()): Promise<void> {\n const configPath = resolve(targetDir, '.aitestrc.json');\n try {\n await writeFile(configPath, JSON.stringify(config, null, 2), 'utf-8');\n logger.success(`Saved config to ${configPath}`);\n } catch (error) {\n logger.error(`Failed to save config: ${error}`);\n throw error;\n }\n}\n","import chalk from 'chalk';\nimport ora from 'ora';\n\nexport const logger = {\n info: (msg: string) => console.log(chalk.blue('ℹ'), msg),\n success: (msg: string) => console.log(chalk.green('✔'), msg),\n error: (msg: string) => console.error(chalk.red('✖'), msg),\n warn: (msg: string) => console.warn(chalk.yellow('⚠'), msg),\n log: (msg: string) => console.log(msg),\n spinner: (text: string) => ora(text),\n};\n","import { generateText } from 'ai';\nimport { createOpenAI } from '@ai-sdk/openai';\nimport { createAnthropic } from '@ai-sdk/anthropic';\nimport { createGoogleGenerativeAI } from '@ai-sdk/google';\nimport { createDeepSeek } from '@ai-sdk/deepseek';\nimport { AITestConfig } from './config.js';\nimport * as dotenv from 'dotenv';\n\ndotenv.config();\n\nexport function getAIModel(config: AITestConfig) {\n const providerName = config.provider.toLowerCase();\n \n if (providerName === 'openai') {\n const openai = createOpenAI({\n apiKey: config.apiKey || process.env.OPENAI_API_KEY,\n });\n return openai(config.model || 'gpt-4o');\n } \n \n if (providerName === 'anthropic') {\n const anthropic = createAnthropic({\n apiKey: config.apiKey || process.env.ANTHROPIC_API_KEY,\n });\n return anthropic(config.model || 'claude-3-5-sonnet-20240620');\n }\n\n if (providerName === 'gemini' || providerName === 'google') {\n const google = createGoogleGenerativeAI({\n apiKey: config.apiKey || process.env.GEMINI_API_KEY || process.env.GOOGLE_GENERATIVE_AI_API_KEY,\n });\n return google(config.model || 'gemini-2.5-flash');\n }\n\n if (providerName === 'deepseek') {\n const deepseek = createDeepSeek({\n apiKey: config.apiKey || process.env.DEEPSEEK_API_KEY,\n });\n return deepseek(config.model || 'deepseek-coder');\n }\n\n if (providerName === 'ollama') {\n const ollama = createOpenAI({\n baseURL: 'http://localhost:11434/v1',\n apiKey: 'ollama', // API key isn't strictly needed for local Ollama\n });\n return ollama(config.model || 'llama3.1');\n }\n\n if (providerName === 'custom') {\n const custom = createOpenAI({\n baseURL: config.baseURL,\n apiKey: config.apiKey || process.env.CUSTOM_API_KEY || 'custom',\n headers: config.customHeaders,\n });\n return custom(config.model || 'local-model');\n }\n\n throw new Error(`Unsupported AI provider: ${config.provider}`);\n}\n\nexport async function askAI(config: AITestConfig, system: string, prompt: string) {\n const model = getAIModel(config);\n \n const { text } = await generateText({\n model,\n system,\n prompt,\n maxRetries: 5,\n temperature: config.temperature || 0.1,\n });\n\n return text;\n}\n","import { readFileSync, existsSync } from 'fs';\nimport { resolve } from 'path';\n\nexport interface ProjectInfo {\n language: 'javascript' | 'typescript';\n testRunner: 'jest' | 'vitest' | 'mocha' | 'unknown';\n framework: 'react' | 'vue' | 'angular' | 'node' | 'unknown';\n packageManager: 'npm' | 'yarn' | 'pnpm' | 'bun';\n}\n\nexport function detectProjectInfo(targetDir: string = process.cwd()): ProjectInfo {\n const packageJsonPath = resolve(targetDir, 'package.json');\n const tsconfigPath = resolve(targetDir, 'tsconfig.json');\n \n let packageJson: any = {};\n if (existsSync(packageJsonPath)) {\n try {\n packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\n } catch (e) {\n // ignore\n }\n }\n\n const allDeps = {\n ...(packageJson.dependencies || {}),\n ...(packageJson.devDependencies || {})\n };\n\n const language = existsSync(tsconfigPath) || allDeps['typescript'] ? 'typescript' : 'javascript';\n \n let testRunner: ProjectInfo['testRunner'] = 'unknown';\n if (allDeps['jest']) testRunner = 'jest';\n else if (allDeps['vitest']) testRunner = 'vitest';\n else if (allDeps['mocha']) testRunner = 'mocha';\n\n let framework: ProjectInfo['framework'] = 'unknown';\n if (allDeps['react']) framework = 'react';\n else if (allDeps['vue']) framework = 'vue';\n else if (allDeps['@angular/core']) framework = 'angular';\n else if (allDeps['express'] || allDeps['@nestjs/core'] || allDeps['fastify']) framework = 'node';\n\n let packageManager: ProjectInfo['packageManager'] = 'npm';\n if (existsSync(resolve(targetDir, 'pnpm-lock.yaml'))) packageManager = 'pnpm';\n else if (existsSync(resolve(targetDir, 'yarn.lock'))) packageManager = 'yarn';\n else if (existsSync(resolve(targetDir, 'bun.lockb'))) packageManager = 'bun';\n\n return { language, testRunner, framework, packageManager };\n}\n"],"mappings":"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","names":["config","config"]}
@@ -0,0 +1,5 @@
1
+ import { Command } from 'commander';
2
+
3
+ declare const scanCommand: Command;
4
+
5
+ export { scanCommand };
@@ -0,0 +1,140 @@
1
+ // src/commands/scan.ts
2
+ import { Command } from "commander";
3
+
4
+ // src/core/logger.ts
5
+ import chalk from "chalk";
6
+ import ora from "ora";
7
+ var logger = {
8
+ info: (msg) => console.log(chalk.blue("\u2139"), msg),
9
+ success: (msg) => console.log(chalk.green("\u2714"), msg),
10
+ error: (msg) => console.error(chalk.red("\u2716"), msg),
11
+ warn: (msg) => console.warn(chalk.yellow("\u26A0"), msg),
12
+ log: (msg) => console.log(msg),
13
+ spinner: (text) => ora(text)
14
+ };
15
+
16
+ // src/core/detector.ts
17
+ import { readFileSync, existsSync } from "fs";
18
+ import { resolve } from "path";
19
+ function detectProjectInfo(targetDir = process.cwd()) {
20
+ const packageJsonPath = resolve(targetDir, "package.json");
21
+ const tsconfigPath = resolve(targetDir, "tsconfig.json");
22
+ let packageJson = {};
23
+ if (existsSync(packageJsonPath)) {
24
+ try {
25
+ packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
26
+ } catch (e) {
27
+ }
28
+ }
29
+ const allDeps = {
30
+ ...packageJson.dependencies || {},
31
+ ...packageJson.devDependencies || {}
32
+ };
33
+ const language = existsSync(tsconfigPath) || allDeps["typescript"] ? "typescript" : "javascript";
34
+ let testRunner = "unknown";
35
+ if (allDeps["jest"]) testRunner = "jest";
36
+ else if (allDeps["vitest"]) testRunner = "vitest";
37
+ else if (allDeps["mocha"]) testRunner = "mocha";
38
+ let framework = "unknown";
39
+ if (allDeps["react"]) framework = "react";
40
+ else if (allDeps["vue"]) framework = "vue";
41
+ else if (allDeps["@angular/core"]) framework = "angular";
42
+ else if (allDeps["express"] || allDeps["@nestjs/core"] || allDeps["fastify"]) framework = "node";
43
+ let packageManager = "npm";
44
+ if (existsSync(resolve(targetDir, "pnpm-lock.yaml"))) packageManager = "pnpm";
45
+ else if (existsSync(resolve(targetDir, "yarn.lock"))) packageManager = "yarn";
46
+ else if (existsSync(resolve(targetDir, "bun.lockb"))) packageManager = "bun";
47
+ return { language, testRunner, framework, packageManager };
48
+ }
49
+
50
+ // src/commands/scan.ts
51
+ import fg from "fast-glob";
52
+ var scanCommand = new Command("scan").description("Analyze the repository for testing health").action(async () => {
53
+ logger.info("Scanning repository...\n");
54
+ const spinner = logger.spinner("Analyzing files and configuration...").start();
55
+ const projectInfo = detectProjectInfo();
56
+ try {
57
+ const sourceFiles = await fg(["**/*.{js,ts,jsx,tsx}"], {
58
+ ignore: [
59
+ "**/node_modules/**",
60
+ "**/dist/**",
61
+ "**/build/**",
62
+ "**/coverage/**",
63
+ "**/*.test.*",
64
+ "**/*.spec.*",
65
+ "**/vite.config.*",
66
+ "**/tsup.config.*",
67
+ "**/.*"
68
+ ],
69
+ cwd: process.cwd()
70
+ });
71
+ const testFiles = await fg(["**/*.test.{js,ts,jsx,tsx}", "**/*.spec.{js,ts,jsx,tsx}"], {
72
+ ignore: ["**/node_modules/**", "**/dist/**", "**/build/**", "**/coverage/**"],
73
+ cwd: process.cwd()
74
+ });
75
+ const sourceBases = sourceFiles.map((f) => {
76
+ const parts = f.split("/");
77
+ const name = parts[parts.length - 1];
78
+ return name.replace(/\.(js|ts|jsx|tsx)$/, "");
79
+ });
80
+ const testBases = testFiles.map((f) => {
81
+ const parts = f.split("/");
82
+ const name = parts[parts.length - 1];
83
+ return name.replace(/\.(test|spec)\.(js|ts|jsx|tsx)$/, "");
84
+ });
85
+ let coveredFiles = 0;
86
+ const missingTestsFiles = [];
87
+ for (let i = 0; i < sourceFiles.length; i++) {
88
+ const base = sourceBases[i];
89
+ if (testBases.includes(base)) {
90
+ coveredFiles++;
91
+ } else {
92
+ missingTestsFiles.push(sourceFiles[i]);
93
+ }
94
+ }
95
+ const coverageEstimate = sourceFiles.length > 0 ? Math.round(coveredFiles / sourceFiles.length * 100) : 100;
96
+ let riskScore = "Low";
97
+ let riskColor = "\x1B[32m";
98
+ if (coverageEstimate < 40) {
99
+ riskScore = "High";
100
+ riskColor = "\x1B[31m";
101
+ } else if (coverageEstimate < 70) {
102
+ riskScore = "Medium";
103
+ riskColor = "\x1B[33m";
104
+ }
105
+ const resetColor = "\x1B[0m";
106
+ spinner.succeed("Analysis complete\n");
107
+ console.log("\u{1F4CA} Repository Scan Report\n");
108
+ console.log("--- Architecture ---");
109
+ console.log(`Language: ${projectInfo.language}`);
110
+ console.log(`Framework: ${projectInfo.framework}`);
111
+ console.log(`Test Runner: ${projectInfo.testRunner}`);
112
+ console.log(`Package Manager: ${projectInfo.packageManager}
113
+ `);
114
+ console.log("--- Metrics ---");
115
+ console.log(`Total Source Files: ${sourceFiles.length}`);
116
+ console.log(`Total Test Files: ${testFiles.length}`);
117
+ console.log(`Coverage Estimate: ${coverageEstimate}% (based on file pairing)`);
118
+ console.log(`Risk Score: ${riskColor}${riskScore}${resetColor}
119
+ `);
120
+ console.log("--- Missing Tests (Top 10) ---");
121
+ if (missingTestsFiles.length === 0) {
122
+ console.log("All source files appear to have matching test files! \u{1F389}");
123
+ } else {
124
+ const displayLimit = Math.min(10, missingTestsFiles.length);
125
+ for (let i = 0; i < displayLimit; i++) {
126
+ console.log(`- ${missingTestsFiles[i]}`);
127
+ }
128
+ if (missingTestsFiles.length > 10) {
129
+ console.log(`... and ${missingTestsFiles.length - 10} more.`);
130
+ }
131
+ console.log("\nRun `aitest generate --all` to automatically generate missing tests.");
132
+ }
133
+ } catch (error) {
134
+ spinner.fail(`Scan failed: ${error.message}`);
135
+ }
136
+ });
137
+ export {
138
+ scanCommand
139
+ };
140
+ //# sourceMappingURL=scan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/commands/scan.ts","../../src/core/logger.ts","../../src/core/detector.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { logger } from '../core/logger.js';\nimport { detectProjectInfo } from '../core/detector.js';\nimport fg from 'fast-glob';\n\nexport const scanCommand = new Command('scan')\n .description('Analyze the repository for testing health')\n .action(async () => {\n logger.info('Scanning repository...\\n');\n\n const spinner = logger.spinner('Analyzing files and configuration...').start();\n\n // 1. Detect Framework & Architecture\n const projectInfo = detectProjectInfo();\n \n // 2. Scan files\n try {\n const sourceFiles = await fg(['**/*.{js,ts,jsx,tsx}'], {\n ignore: [\n '**/node_modules/**', \n '**/dist/**', \n '**/build/**', \n '**/coverage/**', \n '**/*.test.*', \n '**/*.spec.*', \n '**/vite.config.*',\n '**/tsup.config.*',\n '**/.*'\n ],\n cwd: process.cwd()\n });\n\n const testFiles = await fg(['**/*.test.{js,ts,jsx,tsx}', '**/*.spec.{js,ts,jsx,tsx}'], {\n ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/coverage/**'],\n cwd: process.cwd()\n });\n\n // 3. Estimate Coverage and Missing Tests\n // A simple heuristic: how many source files have a matching test file?\n const sourceBases = sourceFiles.map(f => {\n const parts = f.split('/');\n const name = parts[parts.length - 1];\n return name.replace(/\\.(js|ts|jsx|tsx)$/, '');\n });\n\n const testBases = testFiles.map(f => {\n const parts = f.split('/');\n const name = parts[parts.length - 1];\n return name.replace(/\\.(test|spec)\\.(js|ts|jsx|tsx)$/, '');\n });\n\n let coveredFiles = 0;\n const missingTestsFiles: string[] = [];\n\n for (let i = 0; i < sourceFiles.length; i++) {\n const base = sourceBases[i];\n if (testBases.includes(base)) {\n coveredFiles++;\n } else {\n missingTestsFiles.push(sourceFiles[i]);\n }\n }\n\n const coverageEstimate = sourceFiles.length > 0 \n ? Math.round((coveredFiles / sourceFiles.length) * 100) \n : 100;\n\n // 4. Calculate Risk Score\n let riskScore = 'Low';\n let riskColor = '\\x1b[32m'; // Green\n if (coverageEstimate < 40) {\n riskScore = 'High';\n riskColor = '\\x1b[31m'; // Red\n } else if (coverageEstimate < 70) {\n riskScore = 'Medium';\n riskColor = '\\x1b[33m'; // Yellow\n }\n const resetColor = '\\x1b[0m';\n\n spinner.succeed('Analysis complete\\n');\n\n console.log('📊 Repository Scan Report\\n');\n \n console.log('--- Architecture ---');\n console.log(`Language: ${projectInfo.language}`);\n console.log(`Framework: ${projectInfo.framework}`);\n console.log(`Test Runner: ${projectInfo.testRunner}`);\n console.log(`Package Manager: ${projectInfo.packageManager}\\n`);\n\n console.log('--- Metrics ---');\n console.log(`Total Source Files: ${sourceFiles.length}`);\n console.log(`Total Test Files: ${testFiles.length}`);\n console.log(`Coverage Estimate: ${coverageEstimate}% (based on file pairing)`);\n console.log(`Risk Score: ${riskColor}${riskScore}${resetColor}\\n`);\n\n console.log('--- Missing Tests (Top 10) ---');\n if (missingTestsFiles.length === 0) {\n console.log('All source files appear to have matching test files! 🎉');\n } else {\n const displayLimit = Math.min(10, missingTestsFiles.length);\n for (let i = 0; i < displayLimit; i++) {\n console.log(`- ${missingTestsFiles[i]}`);\n }\n if (missingTestsFiles.length > 10) {\n console.log(`... and ${missingTestsFiles.length - 10} more.`);\n }\n console.log('\\nRun `aitest generate --all` to automatically generate missing tests.');\n }\n\n } catch (error: any) {\n spinner.fail(`Scan failed: ${error.message}`);\n }\n });\n","import chalk from 'chalk';\nimport ora from 'ora';\n\nexport const logger = {\n info: (msg: string) => console.log(chalk.blue('ℹ'), msg),\n success: (msg: string) => console.log(chalk.green('✔'), msg),\n error: (msg: string) => console.error(chalk.red('✖'), msg),\n warn: (msg: string) => console.warn(chalk.yellow('⚠'), msg),\n log: (msg: string) => console.log(msg),\n spinner: (text: string) => ora(text),\n};\n","import { readFileSync, existsSync } from 'fs';\nimport { resolve } from 'path';\n\nexport interface ProjectInfo {\n language: 'javascript' | 'typescript';\n testRunner: 'jest' | 'vitest' | 'mocha' | 'unknown';\n framework: 'react' | 'vue' | 'angular' | 'node' | 'unknown';\n packageManager: 'npm' | 'yarn' | 'pnpm' | 'bun';\n}\n\nexport function detectProjectInfo(targetDir: string = process.cwd()): ProjectInfo {\n const packageJsonPath = resolve(targetDir, 'package.json');\n const tsconfigPath = resolve(targetDir, 'tsconfig.json');\n \n let packageJson: any = {};\n if (existsSync(packageJsonPath)) {\n try {\n packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\n } catch (e) {\n // ignore\n }\n }\n\n const allDeps = {\n ...(packageJson.dependencies || {}),\n ...(packageJson.devDependencies || {})\n };\n\n const language = existsSync(tsconfigPath) || allDeps['typescript'] ? 'typescript' : 'javascript';\n \n let testRunner: ProjectInfo['testRunner'] = 'unknown';\n if (allDeps['jest']) testRunner = 'jest';\n else if (allDeps['vitest']) testRunner = 'vitest';\n else if (allDeps['mocha']) testRunner = 'mocha';\n\n let framework: ProjectInfo['framework'] = 'unknown';\n if (allDeps['react']) framework = 'react';\n else if (allDeps['vue']) framework = 'vue';\n else if (allDeps['@angular/core']) framework = 'angular';\n else if (allDeps['express'] || allDeps['@nestjs/core'] || allDeps['fastify']) framework = 'node';\n\n let packageManager: ProjectInfo['packageManager'] = 'npm';\n if (existsSync(resolve(targetDir, 'pnpm-lock.yaml'))) packageManager = 'pnpm';\n else if (existsSync(resolve(targetDir, 'yarn.lock'))) packageManager = 'yarn';\n else if (existsSync(resolve(targetDir, 'bun.lockb'))) packageManager = 'bun';\n\n return { language, testRunner, framework, packageManager };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,2 @@
1
+
2
+ export { }