更新DACP白皮书的更新日期至2025-01-19;在DACPConfigManager中优化配置管理,支持项目级和用户级配置的优先级处理,增强错误提示信息,更新相关方法以支持异步操作。
This commit is contained in:
254
docs/dacp-config-management.md
Normal file
254
docs/dacp-config-management.md
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
# DACP配置管理指南
|
||||||
|
|
||||||
|
**版本**: 1.0.0
|
||||||
|
**更新日期**: 2025-01-19
|
||||||
|
**作者**: Sean
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
DACP配置管理系统支持分层配置策略,实现了项目级配置优先、用户级配置回退的灵活配置管理机制。这允许团队在项目中共享配置,同时保持个人配置的独立性。
|
||||||
|
|
||||||
|
## 配置优先级
|
||||||
|
|
||||||
|
```
|
||||||
|
项目级配置 (.promptx/dacp/) > 用户级配置 (~/.promptx/dacp/)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 优先级说明
|
||||||
|
|
||||||
|
1. **项目级配置** - 位于当前项目的 `.promptx/dacp/` 目录
|
||||||
|
- 优先级最高
|
||||||
|
- 适合团队共享的项目配置
|
||||||
|
- 可以版本控制管理
|
||||||
|
|
||||||
|
2. **用户级配置** - 位于用户主目录的 `~/.promptx/dacp/` 目录
|
||||||
|
- 作为回退选择
|
||||||
|
- 个人私有配置
|
||||||
|
- 跨项目通用配置
|
||||||
|
|
||||||
|
## 配置文件结构
|
||||||
|
|
||||||
|
### 邮件服务配置示例 (send_email.json)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"provider": "gmail",
|
||||||
|
"smtp": {
|
||||||
|
"user": "your-email@gmail.com",
|
||||||
|
"password": "your-app-password"
|
||||||
|
},
|
||||||
|
"sender": {
|
||||||
|
"name": "Your Name",
|
||||||
|
"email": "your-email@gmail.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 支持的邮件服务商
|
||||||
|
|
||||||
|
- **Gmail**: smtp.gmail.com:587
|
||||||
|
- **Outlook**: smtp-mail.outlook.com:587
|
||||||
|
- **QQ邮箱**: smtp.qq.com:465
|
||||||
|
- **163邮箱**: smtp.163.com:465
|
||||||
|
- **126邮箱**: smtp.126.com:465
|
||||||
|
|
||||||
|
## 使用方式
|
||||||
|
|
||||||
|
### 1. 项目级配置(推荐)
|
||||||
|
|
||||||
|
创建项目级配置文件:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 创建配置目录
|
||||||
|
mkdir -p .promptx/dacp
|
||||||
|
|
||||||
|
# 创建邮件配置文件
|
||||||
|
cat > .promptx/dacp/send_email.json << 'EOF'
|
||||||
|
{
|
||||||
|
"provider": "gmail",
|
||||||
|
"smtp": {
|
||||||
|
"user": "project-team@gmail.com",
|
||||||
|
"password": "project-app-password"
|
||||||
|
},
|
||||||
|
"sender": {
|
||||||
|
"name": "Project Team",
|
||||||
|
"email": "project-team@gmail.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 用户级配置(个人回退)
|
||||||
|
|
||||||
|
创建用户级配置文件:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 创建用户配置目录
|
||||||
|
mkdir -p ~/.promptx/dacp
|
||||||
|
|
||||||
|
# 创建个人邮件配置
|
||||||
|
cat > ~/.promptx/dacp/send_email.json << 'EOF'
|
||||||
|
{
|
||||||
|
"provider": "gmail",
|
||||||
|
"smtp": {
|
||||||
|
"user": "personal@gmail.com",
|
||||||
|
"password": "personal-app-password"
|
||||||
|
},
|
||||||
|
"sender": {
|
||||||
|
"name": "Personal Name",
|
||||||
|
"email": "personal@gmail.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
## Gmail配置特别说明
|
||||||
|
|
||||||
|
### 应用专用密码设置
|
||||||
|
|
||||||
|
Gmail用户需要使用应用专用密码:
|
||||||
|
|
||||||
|
1. 进入 [Google 账户设置](https://myaccount.google.com)
|
||||||
|
2. 启用两步验证
|
||||||
|
3. 生成应用专用密码
|
||||||
|
4. 在配置文件中使用生成的密码
|
||||||
|
|
||||||
|
### 配置示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"provider": "gmail",
|
||||||
|
"smtp": {
|
||||||
|
"user": "yourname@gmail.com",
|
||||||
|
"password": "abcd efgh ijkl mnop" // 应用专用密码(16位,含空格)
|
||||||
|
},
|
||||||
|
"sender": {
|
||||||
|
"name": "Your Name",
|
||||||
|
"email": "yourname@gmail.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 配置管理命令
|
||||||
|
|
||||||
|
### 检查配置状态
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const DACPConfigManager = require('./src/lib/utils/DACPConfigManager')
|
||||||
|
const configManager = new DACPConfigManager()
|
||||||
|
|
||||||
|
// 检查是否有配置(任意级别)
|
||||||
|
const hasConfig = await configManager.hasActionConfig('send_email')
|
||||||
|
|
||||||
|
// 检查项目级配置
|
||||||
|
const hasProjectConfig = await configManager.hasProjectActionConfig('send_email')
|
||||||
|
|
||||||
|
// 检查用户级配置
|
||||||
|
const hasUserConfig = await configManager.hasUserActionConfig('send_email')
|
||||||
|
```
|
||||||
|
|
||||||
|
### 读取配置
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 读取配置(自动优先级选择)
|
||||||
|
const config = await configManager.readActionConfig('send_email')
|
||||||
|
|
||||||
|
// 明确读取项目级配置
|
||||||
|
const projectConfig = await configManager.readProjectActionConfig('send_email')
|
||||||
|
|
||||||
|
// 明确读取用户级配置
|
||||||
|
const userConfig = await configManager.readUserActionConfig('send_email')
|
||||||
|
```
|
||||||
|
|
||||||
|
### 写入配置
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const emailConfig = {
|
||||||
|
provider: "gmail",
|
||||||
|
smtp: {
|
||||||
|
user: "example@gmail.com",
|
||||||
|
password: "app-password"
|
||||||
|
},
|
||||||
|
sender: {
|
||||||
|
name: "Example User",
|
||||||
|
email: "example@gmail.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入项目级配置
|
||||||
|
await configManager.writeProjectActionConfig('send_email', emailConfig)
|
||||||
|
|
||||||
|
// 写入用户级配置
|
||||||
|
await configManager.writeUserActionConfig('send_email', emailConfig)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
### 1. 团队协作
|
||||||
|
|
||||||
|
- **项目配置**: 使用通用的项目配置,可以提交到版本控制
|
||||||
|
- **敏感信息**: 个人敏感信息(如密码)使用用户级配置
|
||||||
|
- **配置模板**: 在项目中提供配置模板,团队成员复制后修改
|
||||||
|
|
||||||
|
### 2. 安全性
|
||||||
|
|
||||||
|
- **不要提交密码**: 项目级配置可以包含结构,但不应包含真实密码
|
||||||
|
- **使用应用密码**: Gmail等服务使用应用专用密码
|
||||||
|
- **权限控制**: 确保配置文件权限设置合理
|
||||||
|
|
||||||
|
### 3. 配置继承
|
||||||
|
|
||||||
|
当前版本支持完全覆盖模式:
|
||||||
|
- 如果存在项目级配置,完全使用项目级配置
|
||||||
|
- 如果不存在项目级配置,回退到用户级配置
|
||||||
|
- 未来版本可能支持配置合并模式
|
||||||
|
|
||||||
|
## 错误处理
|
||||||
|
|
||||||
|
### 常见错误和解决方案
|
||||||
|
|
||||||
|
1. **配置文件不存在**
|
||||||
|
```
|
||||||
|
解决方案: 按照上述步骤创建配置文件
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **项目目录无法获取**
|
||||||
|
```
|
||||||
|
解决方案: 确保在PromptX项目目录中运行,或使用用户级配置
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **SMTP认证失败**
|
||||||
|
```
|
||||||
|
解决方案: 检查用户名、密码和服务器配置
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Gmail应用密码问题**
|
||||||
|
```
|
||||||
|
解决方案: 重新生成应用专用密码,确保格式正确
|
||||||
|
```
|
||||||
|
|
||||||
|
## 版本兼容性
|
||||||
|
|
||||||
|
- **向后兼容**: 现有用户级配置继续工作
|
||||||
|
- **API兼容**: 原有API方法保持不变
|
||||||
|
- **渐进升级**: 可以逐步迁移到项目级配置
|
||||||
|
|
||||||
|
## 扩展功能
|
||||||
|
|
||||||
|
### 未来规划
|
||||||
|
|
||||||
|
1. **配置合并模式**: 支持项目级和用户级配置的智能合并
|
||||||
|
2. **配置验证**: 增强的配置验证和错误提示
|
||||||
|
3. **配置模板**: 内置常用配置模板
|
||||||
|
4. **环境变量支持**: 支持通过环境变量覆盖配置
|
||||||
|
5. **配置加密**: 敏感信息的加密存储
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 参考资料
|
||||||
|
|
||||||
|
- [DACP白皮书](./dacp-whitepaper.md)
|
||||||
|
- [MCP集成指南](./mcp-integration-guide.md)
|
||||||
|
- [PromptX架构原理](./promptx-architecture-principle.md)
|
||||||
@ -4,7 +4,7 @@
|
|||||||
**版本**: 1.0.0-draft
|
**版本**: 1.0.0-draft
|
||||||
**作者**: Deepractice Team
|
**作者**: Deepractice Team
|
||||||
**创建日期**: 2024-12-17
|
**创建日期**: 2024-12-17
|
||||||
**更新日期**: 2024-12-17
|
**更新日期**: 2025-01-19
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -144,7 +144,7 @@ async function executeSendEmail(emailData, context) {
|
|||||||
const validation = configManager.validateEmailConfig(config)
|
const validation = configManager.validateEmailConfig(config)
|
||||||
if (!validation.valid) {
|
if (!validation.valid) {
|
||||||
// 配置无效,抛出友好错误
|
// 配置无效,抛出友好错误
|
||||||
const errorMessage = configManager.generateConfigErrorMessage('send_email', validation)
|
const errorMessage = await configManager.generateConfigErrorMessage('send_email', validation)
|
||||||
throw new Error(errorMessage)
|
throw new Error(errorMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,7 +169,7 @@ async function executeDemoSendEmail(emailData, context) {
|
|||||||
await new Promise(resolve => setTimeout(resolve, 100));
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
|
||||||
const configManager = new DACPConfigManager()
|
const configManager = new DACPConfigManager()
|
||||||
const configHint = configManager.generateConfigErrorMessage('send_email')
|
const configHint = await configManager.generateConfigErrorMessage('send_email')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message_id: `demo_msg_${Date.now()}`,
|
message_id: `demo_msg_${Date.now()}`,
|
||||||
|
|||||||
@ -1,71 +1,214 @@
|
|||||||
const fs = require('fs-extra')
|
const fs = require('fs-extra')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const os = require('os')
|
const os = require('os')
|
||||||
|
const { getDirectoryService } = require('./DirectoryService')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DACP用户级配置管理器
|
* DACP配置管理器
|
||||||
* 管理 ~/.promptx/dacp/ 下的配置文件
|
* 支持项目级配置优先,用户级配置回退的分层配置策略
|
||||||
|
* 配置优先级:项目级(.promptx/dacp/) > 用户级(~/.promptx/dacp/)
|
||||||
*/
|
*/
|
||||||
class DACPConfigManager {
|
class DACPConfigManager {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.userHome = os.homedir()
|
this.userHome = os.homedir()
|
||||||
this.dacpConfigDir = path.join(this.userHome, '.promptx', 'dacp')
|
this.userDacpConfigDir = path.join(this.userHome, '.promptx', 'dacp')
|
||||||
|
this.directoryService = getDirectoryService()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 确保DACP配置目录存在
|
* 确保用户级DACP配置目录存在
|
||||||
*/
|
*/
|
||||||
async ensureConfigDir() {
|
async ensureUserConfigDir() {
|
||||||
await fs.ensureDir(this.dacpConfigDir)
|
await fs.ensureDir(this.userDacpConfigDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取指定action的配置文件路径
|
* 获取项目级DACP配置目录路径
|
||||||
* @param {string} action - action名称,如 'send_email'
|
* @returns {Promise<string|null>} 项目级配置目录路径或null
|
||||||
* @returns {string} 配置文件完整路径
|
|
||||||
*/
|
*/
|
||||||
getConfigPath(action) {
|
async getProjectConfigDir() {
|
||||||
return path.join(this.dacpConfigDir, `${action}.json`)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 读取action配置
|
|
||||||
* @param {string} action - action名称
|
|
||||||
* @returns {Promise<Object|null>} 配置对象或null
|
|
||||||
*/
|
|
||||||
async readActionConfig(action) {
|
|
||||||
const configPath = this.getConfigPath(action)
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (await fs.pathExists(configPath)) {
|
const promptxDir = await this.directoryService.getPromptXDirectory()
|
||||||
return await fs.readJson(configPath)
|
return path.join(promptxDir, 'dacp')
|
||||||
}
|
|
||||||
return null
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(`读取DACP配置失败 ${action}:`, error.message)
|
console.warn('获取项目级配置目录失败:', error.message)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 写入action配置
|
* 确保项目级DACP配置目录存在
|
||||||
|
* @returns {Promise<string|null>} 项目级配置目录路径或null
|
||||||
|
*/
|
||||||
|
async ensureProjectConfigDir() {
|
||||||
|
const projectConfigDir = await this.getProjectConfigDir()
|
||||||
|
if (projectConfigDir) {
|
||||||
|
await fs.ensureDir(projectConfigDir)
|
||||||
|
return projectConfigDir
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定action的用户级配置文件路径
|
||||||
|
* @param {string} action - action名称,如 'send_email'
|
||||||
|
* @returns {string} 用户级配置文件完整路径
|
||||||
|
*/
|
||||||
|
getUserConfigPath(action) {
|
||||||
|
return path.join(this.userDacpConfigDir, `${action}.json`)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定action的项目级配置文件路径
|
||||||
|
* @param {string} action - action名称,如 'send_email'
|
||||||
|
* @returns {Promise<string|null>} 项目级配置文件完整路径或null
|
||||||
|
*/
|
||||||
|
async getProjectConfigPath(action) {
|
||||||
|
const projectConfigDir = await this.getProjectConfigDir()
|
||||||
|
if (projectConfigDir) {
|
||||||
|
return path.join(projectConfigDir, `${action}.json`)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定action的配置文件路径(用户级,向后兼容)
|
||||||
|
* @param {string} action - action名称,如 'send_email'
|
||||||
|
* @returns {string} 配置文件完整路径
|
||||||
|
* @deprecated 使用getUserConfigPath或getProjectConfigPath
|
||||||
|
*/
|
||||||
|
getConfigPath(action) {
|
||||||
|
return this.getUserConfigPath(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取项目级action配置
|
||||||
|
* @param {string} action - action名称
|
||||||
|
* @returns {Promise<Object|null>} 配置对象或null
|
||||||
|
*/
|
||||||
|
async readProjectActionConfig(action) {
|
||||||
|
try {
|
||||||
|
const projectConfigPath = await this.getProjectConfigPath(action)
|
||||||
|
if (projectConfigPath && await fs.pathExists(projectConfigPath)) {
|
||||||
|
const config = await fs.readJson(projectConfigPath)
|
||||||
|
console.log(`📁 使用项目级DACP配置: ${action}`)
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`读取项目级DACP配置失败 ${action}:`, error.message)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取用户级action配置
|
||||||
|
* @param {string} action - action名称
|
||||||
|
* @returns {Promise<Object|null>} 配置对象或null
|
||||||
|
*/
|
||||||
|
async readUserActionConfig(action) {
|
||||||
|
const userConfigPath = this.getUserConfigPath(action)
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (await fs.pathExists(userConfigPath)) {
|
||||||
|
const config = await fs.readJson(userConfigPath)
|
||||||
|
console.log(`🏠 使用用户级DACP配置: ${action}`)
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`读取用户级DACP配置失败 ${action}:`, error.message)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取action配置(项目级优先,用户级回退)
|
||||||
|
* @param {string} action - action名称
|
||||||
|
* @returns {Promise<Object|null>} 配置对象或null
|
||||||
|
*/
|
||||||
|
async readActionConfig(action) {
|
||||||
|
// 优先级:项目级 > 用户级
|
||||||
|
const projectConfig = await this.readProjectActionConfig(action)
|
||||||
|
if (projectConfig) {
|
||||||
|
return projectConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.readUserActionConfig(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 写入用户级action配置
|
||||||
* @param {string} action - action名称
|
* @param {string} action - action名称
|
||||||
* @param {Object} config - 配置对象
|
* @param {Object} config - 配置对象
|
||||||
*/
|
*/
|
||||||
async writeActionConfig(action, config) {
|
async writeUserActionConfig(action, config) {
|
||||||
await this.ensureConfigDir()
|
await this.ensureUserConfigDir()
|
||||||
const configPath = this.getConfigPath(action)
|
const configPath = this.getUserConfigPath(action)
|
||||||
await fs.writeJson(configPath, config, { spaces: 2 })
|
await fs.writeJson(configPath, config, { spaces: 2 })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查action配置是否存在
|
* 写入项目级action配置
|
||||||
|
* @param {string} action - action名称
|
||||||
|
* @param {Object} config - 配置对象
|
||||||
|
*/
|
||||||
|
async writeProjectActionConfig(action, config) {
|
||||||
|
const projectConfigDir = await this.ensureProjectConfigDir()
|
||||||
|
if (projectConfigDir) {
|
||||||
|
const configPath = path.join(projectConfigDir, `${action}.json`)
|
||||||
|
await fs.writeJson(configPath, config, { spaces: 2 })
|
||||||
|
} else {
|
||||||
|
throw new Error('无法获取项目目录,写入项目级配置失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 写入action配置(向后兼容,写入用户级)
|
||||||
|
* @param {string} action - action名称
|
||||||
|
* @param {Object} config - 配置对象
|
||||||
|
* @deprecated 使用writeUserActionConfig或writeProjectActionConfig
|
||||||
|
*/
|
||||||
|
async writeActionConfig(action, config) {
|
||||||
|
return await this.writeUserActionConfig(action, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查项目级action配置是否存在
|
||||||
|
* @param {string} action - action名称
|
||||||
|
* @returns {Promise<boolean>}
|
||||||
|
*/
|
||||||
|
async hasProjectActionConfig(action) {
|
||||||
|
try {
|
||||||
|
const projectConfigPath = await this.getProjectConfigPath(action)
|
||||||
|
if (!projectConfigPath) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return await fs.pathExists(projectConfigPath)
|
||||||
|
} catch (error) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查用户级action配置是否存在
|
||||||
|
* @param {string} action - action名称
|
||||||
|
* @returns {Promise<boolean>}
|
||||||
|
*/
|
||||||
|
async hasUserActionConfig(action) {
|
||||||
|
const userConfigPath = this.getUserConfigPath(action)
|
||||||
|
return await fs.pathExists(userConfigPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查action配置是否存在(项目级或用户级)
|
||||||
* @param {string} action - action名称
|
* @param {string} action - action名称
|
||||||
* @returns {Promise<boolean>}
|
* @returns {Promise<boolean>}
|
||||||
*/
|
*/
|
||||||
async hasActionConfig(action) {
|
async hasActionConfig(action) {
|
||||||
const configPath = this.getConfigPath(action)
|
const hasProject = await this.hasProjectActionConfig(action)
|
||||||
return await fs.pathExists(configPath)
|
if (hasProject) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return await this.hasUserActionConfig(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -162,17 +305,23 @@ class DACPConfigManager {
|
|||||||
* 生成配置错误提示信息
|
* 生成配置错误提示信息
|
||||||
* @param {string} action - action名称
|
* @param {string} action - action名称
|
||||||
* @param {Object} validation - 验证结果
|
* @param {Object} validation - 验证结果
|
||||||
* @returns {string} 错误提示信息
|
* @returns {Promise<string>} 错误提示信息
|
||||||
*/
|
*/
|
||||||
generateConfigErrorMessage(action, validation = null) {
|
async generateConfigErrorMessage(action, validation = null) {
|
||||||
const configPath = this.getConfigPath(action)
|
const userConfigPath = this.getUserConfigPath(action)
|
||||||
|
const projectConfigPath = await this.getProjectConfigPath(action)
|
||||||
|
|
||||||
let message = `\n📧 DACP邮件服务配置缺失\n\n`
|
let message = `\n📧 DACP邮件服务配置缺失\n\n`
|
||||||
|
|
||||||
if (!validation) {
|
if (!validation) {
|
||||||
// 配置文件不存在
|
// 配置文件不存在
|
||||||
message += `❌ 配置文件不存在: ${configPath}\n\n`
|
message += `❌ 未找到配置文件\n\n`
|
||||||
message += `📝 请创建配置文件,内容如下:\n\n`
|
message += `🔍 查找路径:\n`
|
||||||
|
if (projectConfigPath) {
|
||||||
|
message += ` 项目级: ${projectConfigPath} (优先)\n`
|
||||||
|
}
|
||||||
|
message += ` 用户级: ${userConfigPath} (回退)\n\n`
|
||||||
|
message += `📝 推荐创建项目级配置文件,内容如下:\n\n`
|
||||||
message += `{\n`
|
message += `{\n`
|
||||||
message += ` "provider": "gmail",\n`
|
message += ` "provider": "gmail",\n`
|
||||||
message += ` "smtp": {\n`
|
message += ` "smtp": {\n`
|
||||||
@ -185,6 +334,7 @@ class DACPConfigManager {
|
|||||||
message += ` }\n`
|
message += ` }\n`
|
||||||
message += `}\n\n`
|
message += `}\n\n`
|
||||||
message += `💡 支持的邮件服务商: gmail, outlook, qq, 163, 126\n\n`
|
message += `💡 支持的邮件服务商: gmail, outlook, qq, 163, 126\n\n`
|
||||||
|
message += `🏗️ 配置优先级: 项目级(.promptx/dacp/) > 用户级(~/.promptx/dacp/)\n\n`
|
||||||
message += `🔐 Gmail用户需要使用应用专用密码:\n`
|
message += `🔐 Gmail用户需要使用应用专用密码:\n`
|
||||||
message += ` 1. 进入 Google 账户设置\n`
|
message += ` 1. 进入 Google 账户设置\n`
|
||||||
message += ` 2. 启用两步验证\n`
|
message += ` 2. 启用两步验证\n`
|
||||||
@ -192,7 +342,10 @@ class DACPConfigManager {
|
|||||||
message += ` 4. 使用生成的密码替换上面的 "your-app-password"\n`
|
message += ` 4. 使用生成的密码替换上面的 "your-app-password"\n`
|
||||||
} else {
|
} else {
|
||||||
// 配置不完整
|
// 配置不完整
|
||||||
message += `❌ 配置文件存在但不完整: ${configPath}\n\n`
|
const configLocation = await this.hasProjectActionConfig(action) ?
|
||||||
|
(projectConfigPath ? `项目级: ${projectConfigPath}` : '项目级配置') :
|
||||||
|
`用户级: ${userConfigPath}`
|
||||||
|
message += `❌ 配置文件存在但不完整: ${configLocation}\n\n`
|
||||||
message += `缺少以下配置项:\n`
|
message += `缺少以下配置项:\n`
|
||||||
validation.errors.forEach(error => {
|
validation.errors.forEach(error => {
|
||||||
message += ` • ${error}\n`
|
message += ` • ${error}\n`
|
||||||
|
|||||||
28
src/tests/setup.js
Normal file
28
src/tests/setup.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// Jest测试环境设置文件
|
||||||
|
|
||||||
|
// 全局测试超时设置
|
||||||
|
jest.setTimeout(15000)
|
||||||
|
|
||||||
|
// 控制台输出配置
|
||||||
|
const originalError = console.error
|
||||||
|
console.error = (...args) => {
|
||||||
|
// 过滤掉某些预期的警告信息
|
||||||
|
if (args[0] && typeof args[0] === 'string') {
|
||||||
|
if (args[0].includes('Warning: ReactDOM.render is deprecated')) return
|
||||||
|
if (args[0].includes('Warning: componentWillReceiveProps')) return
|
||||||
|
}
|
||||||
|
originalError(...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟环境变量
|
||||||
|
process.env.NODE_ENV = 'test'
|
||||||
|
process.env.PROMPTX_TEST_MODE = 'true'
|
||||||
|
|
||||||
|
// 清理函数
|
||||||
|
afterEach(() => {
|
||||||
|
// 清理任何测试状态
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
// 最终清理
|
||||||
|
})
|
||||||
226
src/tests/unit/DACPConfigManager.unit.test.js
Normal file
226
src/tests/unit/DACPConfigManager.unit.test.js
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
const fs = require('fs-extra')
|
||||||
|
const path = require('path')
|
||||||
|
const os = require('os')
|
||||||
|
const DACPConfigManager = require('../../lib/utils/DACPConfigManager')
|
||||||
|
|
||||||
|
// Mock DirectoryService
|
||||||
|
jest.mock('../../lib/utils/DirectoryService', () => ({
|
||||||
|
getDirectoryService: () => ({
|
||||||
|
getPromptXDirectory: jest.fn()
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
|
||||||
|
const { getDirectoryService } = require('../../lib/utils/DirectoryService')
|
||||||
|
|
||||||
|
describe('DACPConfigManager - 项目级配置优先', () => {
|
||||||
|
let configManager
|
||||||
|
let mockDirectoryService
|
||||||
|
let tempDir
|
||||||
|
let userConfigDir
|
||||||
|
let projectConfigDir
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
// 创建临时测试目录
|
||||||
|
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'dacp-config-test-'))
|
||||||
|
userConfigDir = path.join(tempDir, 'user', '.promptx', 'dacp')
|
||||||
|
projectConfigDir = path.join(tempDir, 'project', '.promptx', 'dacp')
|
||||||
|
|
||||||
|
// 确保目录存在
|
||||||
|
await fs.ensureDir(userConfigDir)
|
||||||
|
await fs.ensureDir(projectConfigDir)
|
||||||
|
|
||||||
|
// Mock DirectoryService
|
||||||
|
mockDirectoryService = getDirectoryService()
|
||||||
|
mockDirectoryService.getPromptXDirectory.mockResolvedValue(path.join(tempDir, 'project', '.promptx'))
|
||||||
|
|
||||||
|
// 创建配置管理器
|
||||||
|
configManager = new DACPConfigManager()
|
||||||
|
|
||||||
|
// Mock用户目录
|
||||||
|
configManager.userDacpConfigDir = userConfigDir
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
// 清理临时目录
|
||||||
|
await fs.remove(tempDir)
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('配置读取优先级', () => {
|
||||||
|
test('应该优先读取项目级配置', async () => {
|
||||||
|
const projectConfig = {
|
||||||
|
provider: 'gmail',
|
||||||
|
smtp: { user: 'project@gmail.com', password: 'project-pass' },
|
||||||
|
sender: { name: 'Project User', email: 'project@gmail.com' }
|
||||||
|
}
|
||||||
|
|
||||||
|
const userConfig = {
|
||||||
|
provider: 'outlook',
|
||||||
|
smtp: { user: 'user@outlook.com', password: 'user-pass' },
|
||||||
|
sender: { name: 'User Name', email: 'user@outlook.com' }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入两个配置文件
|
||||||
|
await fs.writeJson(path.join(projectConfigDir, 'send_email.json'), projectConfig)
|
||||||
|
await fs.writeJson(path.join(userConfigDir, 'send_email.json'), userConfig)
|
||||||
|
|
||||||
|
const result = await configManager.readActionConfig('send_email')
|
||||||
|
|
||||||
|
expect(result).toEqual(projectConfig)
|
||||||
|
expect(result.smtp.user).toBe('project@gmail.com')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('项目级配置不存在时应该回退到用户级配置', async () => {
|
||||||
|
const userConfig = {
|
||||||
|
provider: 'outlook',
|
||||||
|
smtp: { user: 'user@outlook.com', password: 'user-pass' },
|
||||||
|
sender: { name: 'User Name', email: 'user@outlook.com' }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只写入用户级配置
|
||||||
|
await fs.writeJson(path.join(userConfigDir, 'send_email.json'), userConfig)
|
||||||
|
|
||||||
|
const result = await configManager.readActionConfig('send_email')
|
||||||
|
|
||||||
|
expect(result).toEqual(userConfig)
|
||||||
|
expect(result.smtp.user).toBe('user@outlook.com')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('两个配置都不存在时应该返回null', async () => {
|
||||||
|
const result = await configManager.readActionConfig('send_email')
|
||||||
|
expect(result).toBeNull()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('配置存在性检查', () => {
|
||||||
|
test('hasActionConfig应该检查项目级和用户级配置', async () => {
|
||||||
|
// 无配置时
|
||||||
|
expect(await configManager.hasActionConfig('send_email')).toBe(false)
|
||||||
|
|
||||||
|
// 仅用户级配置时
|
||||||
|
await fs.writeJson(path.join(userConfigDir, 'send_email.json'), {})
|
||||||
|
expect(await configManager.hasActionConfig('send_email')).toBe(true)
|
||||||
|
|
||||||
|
// 同时存在项目级配置时
|
||||||
|
await fs.writeJson(path.join(projectConfigDir, 'send_email.json'), {})
|
||||||
|
expect(await configManager.hasActionConfig('send_email')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('hasProjectActionConfig应该正确检查项目级配置', async () => {
|
||||||
|
expect(await configManager.hasProjectActionConfig('send_email')).toBe(false)
|
||||||
|
|
||||||
|
await fs.writeJson(path.join(projectConfigDir, 'send_email.json'), {})
|
||||||
|
expect(await configManager.hasProjectActionConfig('send_email')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('hasUserActionConfig应该正确检查用户级配置', async () => {
|
||||||
|
expect(await configManager.hasUserActionConfig('send_email')).toBe(false)
|
||||||
|
|
||||||
|
await fs.writeJson(path.join(userConfigDir, 'send_email.json'), {})
|
||||||
|
expect(await configManager.hasUserActionConfig('send_email')).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('配置写入', () => {
|
||||||
|
test('writeProjectActionConfig应该写入项目级配置', async () => {
|
||||||
|
const config = {
|
||||||
|
provider: 'gmail',
|
||||||
|
smtp: { user: 'test@gmail.com', password: 'test-pass' },
|
||||||
|
sender: { name: 'Test User', email: 'test@gmail.com' }
|
||||||
|
}
|
||||||
|
|
||||||
|
await configManager.writeProjectActionConfig('send_email', config)
|
||||||
|
|
||||||
|
const projectConfigPath = path.join(projectConfigDir, 'send_email.json')
|
||||||
|
expect(await fs.pathExists(projectConfigPath)).toBe(true)
|
||||||
|
|
||||||
|
const savedConfig = await fs.readJson(projectConfigPath)
|
||||||
|
expect(savedConfig).toEqual(config)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('writeUserActionConfig应该写入用户级配置', async () => {
|
||||||
|
const config = {
|
||||||
|
provider: 'outlook',
|
||||||
|
smtp: { user: 'test@outlook.com', password: 'test-pass' },
|
||||||
|
sender: { name: 'Test User', email: 'test@outlook.com' }
|
||||||
|
}
|
||||||
|
|
||||||
|
await configManager.writeUserActionConfig('send_email', config)
|
||||||
|
|
||||||
|
const userConfigPath = path.join(userConfigDir, 'send_email.json')
|
||||||
|
expect(await fs.pathExists(userConfigPath)).toBe(true)
|
||||||
|
|
||||||
|
const savedConfig = await fs.readJson(userConfigPath)
|
||||||
|
expect(savedConfig).toEqual(config)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('向后兼容性', () => {
|
||||||
|
test('原有API方法应该保持兼容', async () => {
|
||||||
|
const config = {
|
||||||
|
provider: 'gmail',
|
||||||
|
smtp: { user: 'legacy@gmail.com', password: 'legacy-pass' },
|
||||||
|
sender: { name: 'Legacy User', email: 'legacy@gmail.com' }
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeActionConfig应该写入用户级配置
|
||||||
|
await configManager.writeActionConfig('send_email', config)
|
||||||
|
|
||||||
|
const userConfigPath = path.join(userConfigDir, 'send_email.json')
|
||||||
|
expect(await fs.pathExists(userConfigPath)).toBe(true)
|
||||||
|
|
||||||
|
// getConfigPath应该返回用户级路径
|
||||||
|
expect(configManager.getConfigPath('send_email')).toBe(userConfigPath)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('错误处理', () => {
|
||||||
|
test('DirectoryService失败时应该优雅降级', async () => {
|
||||||
|
// Mock DirectoryService抛出错误
|
||||||
|
mockDirectoryService.getPromptXDirectory.mockRejectedValue(new Error('项目目录不存在'))
|
||||||
|
|
||||||
|
const userConfig = {
|
||||||
|
provider: 'gmail',
|
||||||
|
smtp: { user: 'user@gmail.com', password: 'user-pass' },
|
||||||
|
sender: { name: 'User', email: 'user@gmail.com' }
|
||||||
|
}
|
||||||
|
|
||||||
|
await fs.writeJson(path.join(userConfigDir, 'send_email.json'), userConfig)
|
||||||
|
|
||||||
|
// 应该能够回退到用户级配置
|
||||||
|
const result = await configManager.readActionConfig('send_email')
|
||||||
|
expect(result).toEqual(userConfig)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('项目目录不可写时writeProjectActionConfig应该抛出错误', async () => {
|
||||||
|
mockDirectoryService.getPromptXDirectory.mockRejectedValue(new Error('无法获取项目目录'))
|
||||||
|
|
||||||
|
const config = { provider: 'gmail' }
|
||||||
|
|
||||||
|
await expect(configManager.writeProjectActionConfig('send_email', config))
|
||||||
|
.rejects.toThrow('无法获取项目目录,写入项目级配置失败')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('配置错误提示信息', () => {
|
||||||
|
test('应该生成包含项目级和用户级路径的错误信息', async () => {
|
||||||
|
const errorMessage = await configManager.generateConfigErrorMessage('send_email')
|
||||||
|
|
||||||
|
expect(errorMessage).toContain('DACP邮件服务配置缺失')
|
||||||
|
expect(errorMessage).toContain('项目级:')
|
||||||
|
expect(errorMessage).toContain('用户级:')
|
||||||
|
expect(errorMessage).toContain('配置优先级: 项目级(.promptx/dacp/) > 用户级(~/.promptx/dacp/)')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('应该正确标识当前使用的配置级别', async () => {
|
||||||
|
const validation = { valid: false, errors: ['缺少SMTP配置'] }
|
||||||
|
|
||||||
|
// 创建项目级配置
|
||||||
|
await fs.writeJson(path.join(projectConfigDir, 'send_email.json'), {})
|
||||||
|
|
||||||
|
const errorMessage = await configManager.generateConfigErrorMessage('send_email', validation)
|
||||||
|
expect(errorMessage).toContain('项目级:')
|
||||||
|
expect(errorMessage).toContain('缺少以下配置项')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user