js
复制代码
const fs = require('fs').promises
const path = require('path')
const base = process.cwd()
const axios = require('axios')
const categoryToFilter = ['node_modules', '.git', '.husky', '.vscode'] // 要排除的类目名称
let FILE_NAME = 'packages'
let globalSystemPrompt = null
async function init(state) {
await import('inquirer').then(async (inquirerModule) => {
const inquirer = inquirerModule.default
const globalSystemRolePrompt = [
{
name: '你是一名前端开发工程师',
value: '你是一名前端开发工程师'
},
{
name: '你是一名后端开发工程师',
value: '你是一名后端开发工程师'
},
{
name: '自定义角色',
value: ''
}
]
const arr = [
{
type: 'list',
name: 'globalSystemRolePrompt',
message: '⚡️⚡️⚡️预制您的角色(多选)⚡️⚡️⚡️',
choices: globalSystemRolePrompt,
default: ''
},
{
type: 'input',
name: 'globalSystemPromptContent',
message: '请输入您对于本次的预制Prompt:',
// default: '接下来的对话,我们使用vue3基于antd组件进行了二次开发。在此基础上进行单元测试代码的输出。'
default: '你的工作主要是,基于antd组件进行了二次开发组件的单元测试代码的输出。'
}
]
await inquirer.prompt(state ? arr : [arr[arr.length - 1]]).then(async (answer) => {
if (!answer.globalSystemRolePrompt && !answer.globalSystemPromptContent) {
return
}
globalSystemPrompt = ` ${globalSystemPrompt || ''} ${answer.globalSystemRolePrompt || ''} ${
answer.globalSystemPromptContent || ''
}`
await use_ai('你是什么角色?你都能做什么事情?回答是否可以开始工作', false, null)
})
})
}
const service = axios.create({
// 在这里可以添加其他配置
timeout: 500000
})
// 禁用 Axios 自动重试
service.defaults.retry = 0 // 设置最大重试次数为 0
async function use_ai(question, filePath, allItem) {
const url = API 地址
const api_key = 'API 密钥' // 替换为实际的 API 密钥
const headers = {
'Content-Type': 'application/json',
'api-key': api_key
}
const data = {
temperature: 0,
messages: [
{ role: 'system', content: globalSystemPrompt },
{ role: 'user', content: question }
]
}
console.log('\x1b[31muser:==>', `\x1b[0m${question}`)
let spinner = null
import('ora').then((ora) => {
spinner = ora.default('Loading...').start()
})
await axios
.post(url, data, { headers })
.then(async (response) => {
const mes = allItem?.name ? `${allItem?.name} 组件` : ''
spinner.succeed(`已完成 ${mes} `)
const response_json = response.data
const regex = /```javascript([\s\S]*?)```/
const content = response_json.choices[0]?.message?.content.replace(regex, (match, code) => {
return code.trim() // 去掉代码块两端的空白字符
})
console.log('\x1b[32mGPT:==>', `\x1b[0m${response_json.choices[0]?.message?.content}`)
// const content = response_json.choices[0]?.message?.content
// console.log(content)
if (filePath) {
await fs.writeFile(filePath, content, 'utf-8')
} else {
import('inquirer').then(async (inquirerModule) => {
const inquirer = inquirerModule.default
await inquirer
.prompt([
{
type: 'list',
name: 'isInquire',
// message: '⚡️⚡️⚡️选择需要的预制Prompt(多选)⚡️⚡️⚡️',
message: '⚡️⚡️⚡️是否要继续补充Prompt⚡️⚡️⚡️',
choices: [
{
name: '是',
value: 1
},
{
name: '否',
value: 0
}
],
default: ''
}
])
.then(async (answer) => {
console.log('\x1b[31m预制Prompt:==>', `\x1b[0m${globalSystemPrompt}`)
console.log('\x1b[32mGPT:==>', `\x1b[0m${content}`)
if (answer.isInquire) {
await init(0)
} else {
await inquirerFun()
}
})
})
}
})
.catch((error) => {
spinner.stop()
console.error('Error:', error)
})
}
// 通过chatgpt接口获取需要单测的内容信息及写入文件
/**
*
* @param {Array} vueFiles 需要单测的数据
* @param {String} choicesQuestion 预制的prompt
*/
async function fetchDataAndWriteToFile(vueFiles, choicesQuestion) {
for (const item of vueFiles) {
try {
const folderPath = `${base}/tests/${item.name}` // 文件夹路径
const filePath = path.join(folderPath, `${item.name}.test.js`) // 文件路径
await fs.mkdir(folderPath, { recursive: true })
let pathMes = `单测组件的引入路径为${item.componentPath}`
await use_ai(`${item.content} ${choicesQuestion} ${pathMes}`, filePath, item)
} catch (error) {
console.error('Error:', error)
}
}
}
// 获取需要单测的vue文件内容
/**
* @param {String} directory 根路径
* @returns {Array} vueFiles 获取单测目录的信息
*/
async function readVueFilesRecursively(directory) {
try {
const items = await fs.readdir(directory, { withFileTypes: true })
const vueFiles = [] // 获取单测目录的信息
for (const item of items) {
const itemPath = path.join(directory, item.name)
if (item.isDirectory()) {
const nestedVueFiles = await readVueFilesRecursively(itemPath)
vueFiles.push(...nestedVueFiles)
} else if (item.isFile() && path.extname(item.name) === '.vue') {
const segments = itemPath.split(path.sep)
const index = segments.indexOf('src') + 1
const vueFilesName = segments[index].split('.')[0]
const fileContent = await fs.readFile(itemPath, 'utf-8')
const withoutStyle = fileContent.replace(/<style[^>]*>[\s\S]*?<\/style>/g, '')
/**
* @param {String} name 文件名
* @param {String} componentPath 组件路径
* @param {String} content 组件vue中提出style样式的内容
*/
vueFiles.push({ name: vueFilesName, componentPath: itemPath.split(':')[1] || '', content: withoutStyle })
}
}
return vueFiles
} catch (error) {
console.error(`Error reading directory ${directory}: ${error}`)
return []
}
}
// readVueFilesRecursively(`${base}/${FILE_NAME}`)
// .then(async (vueFiles) => {
// console.log(vueFiles)
// })
// .catch((error) => {
// console.error('Error:', error)
// })
async function inquirerFun() {
// 终端内选择步骤
import('inquirer').then(async (inquirerModule) => {
const inquirer = inquirerModule.default
// 获取项目一级目录
let filesChoices = null
// 获取选择的prompt
let choicesQuestion = null
const questionInformation = [
// {
// name: '已上代码基于ant-design-vue组件二次开发生成单测用例',
// value: '已上代码基于ant-design-vue组件二次开发生成单测用例'
// },
{
name: '断言是否成功挂载组件',
value: '断言是否成功挂载组件'
},
{
name: '断言是否正确传递了属性',
value: '断言是否正确传递了属性'
},
{
name: '断言插槽内容是否被正确渲染',
value: '断言插槽内容是否被正确渲染'
},
{
name: '断言组件 DOM 是否包含指定的类名',
value: '断言组件 DOM 是否包含指定的类名'
},
{
name: '断言点击事件是否被触发',
value: '断言点击事件是否被触发'
},
{
name: '只输出单测代码,禁止输出文字',
value: '只输出单测代码,禁止输出文字'
}
]
try {
const items = await fs.readdir(base, { withFileTypes: true })
filesChoices = items
.filter((item) => item.isDirectory() && !categoryToFilter.includes(item.name))
.map((item) => {
return {
name: item.name,
value: item.name
}
})
} catch (error) {
console.error(`Error reading directory ${base}: ${error}`)
}
await inquirer
.prompt([
{
type: 'list',
name: 'ExecutionTest',
message: '⚡️⚡️⚡️选择自动化生成单测用例方案⚡️⚡️⚡️',
choices: [
{ name: '全量自动化用例', value: 1 },
{ name: '单文件自动化用例', value: 2 },
{ name: '放弃生成用例', value: 0 }
],
default: true
},
{
type: 'list',
name: 'ExecutionTestFile',
message: '⚡️⚡️⚡️请选择要访问的文件目录⚡️⚡️⚡️',
choices: filesChoices,
default: '',
when: (answers) => {
return answers.ExecutionTest
}
},
{
type: 'checkbox',
name: 'ExecutionTestQuestion',
// message: '⚡️⚡️⚡️选择需要的预制Prompt(多选)⚡️⚡️⚡️',
message: '⚡️⚡️⚡️预制的提问信息(多选)⚡️⚡️⚡️',
choices: questionInformation,
default: '',
when: (answers) => {
return answers.ExecutionTest && answers.ExecutionTestFile
}
},
{
type: 'input',
name: 'customQuestion',
// message: '请输入您自定义的Prompt:',
message: '请输入您自定义的提问信息:',
when: (answers) => {
return answers.ExecutionTest
}
}
])
.then(async (answer) => {
// 取消构建
if (answer.ExecutionTest == 0) {
return
}
// 执行的目录 base
FILE_NAME = answer.ExecutionTestFile
// 选择预制提问信息 + 手动输入的提问信息
choicesQuestion = `${answer.ExecutionTestQuestion.join(',')}${
answer.ExecutionTestQuestion.length && answer.customQuestion ? ',' : '。'
} ${answer.customQuestion}`
if (!choicesQuestion) {
console.error('***请选择或者输入Prompt***')
return
}
// 全链路构建
if (answer.ExecutionTest == 1) {
// console.log('全链路自动构建', FILE_NAME, choicesQuestion)
readVueFilesRecursively(`${base}/${FILE_NAME}`)
.then(async (vueFiles) => {
if (!vueFiles.length) {
return
}
await fetchDataAndWriteToFile(vueFiles, choicesQuestion)
})
.catch((error) => {
console.error('Error:', error)
})
}
// 选择性构建
if (answer.ExecutionTest == 2) {
readVueFilesRecursively(`${base}/${FILE_NAME}`)
.then(async (vueFiles) => {
if (!vueFiles.length) {
return
}
await inquirer
.prompt([
{
type: 'checkbox',
name: 'aloneFileName',
message: '⚡️⚡️⚡️请选择单测组件⚡️⚡️⚡️',
choices: vueFiles.map((item) => {
return {
name: item.name,
value: item.name
}
}),
default: ''
}
])
.then(async (item) => {
if (!item.aloneFileName) {
return
}
const aloneFileArr = vueFiles.filter((file) => file.name == item.aloneFileName)
// console.log('单独构建单测', FILE_NAME, choicesQuestion, item.aloneFileName, aloneFileArr)
await fetchDataAndWriteToFile(aloneFileArr, choicesQuestion)
})
})
.catch((error) => {
console.error('Error:', error)
})
}
})
})
}
init(1)