重构:引入DirectoryService以优化路径解析和项目根目录查找
- 在多个协议实现中(如ProjectProtocol、PackageProtocol等)引入DirectoryService,替换了直接的路径处理逻辑,增强了路径解析的智能性和可靠性。 - 更新了相关方法以支持异步操作,确保在查找项目根目录和注册表路径时能够优雅地处理错误并回退到默认路径。 - 在PromptXConfig中动态计算.promptx目录路径,提升了配置管理的灵活性。 此改动旨在提升代码的可读性和一致性,同时为未来的扩展打下基础。
This commit is contained in:
@ -36,9 +36,12 @@ class MCPServerCommand {
|
||||
}
|
||||
}
|
||||
|
||||
// 基本调试信息
|
||||
this.log(`📂 最终工作目录: ${process.cwd()}`);
|
||||
this.log(`📋 预期记忆文件路径: ${require('path').join(process.cwd(), '.promptx/memory/declarative.md')}`);
|
||||
|
||||
// DirectoryService路径信息将在需要时异步获取
|
||||
|
||||
// 输出完整调试信息
|
||||
if (this.debug) {
|
||||
this.log(`🔍 完整调试信息: ${JSON.stringify(getDebugInfo(), null, 2)}`);
|
||||
|
||||
@ -105,11 +105,11 @@ class ActionCommand extends BasePouchCommand {
|
||||
const relativePath = filePath.replace('@package://', '')
|
||||
filePath = await packageProtocol.resolvePath(relativePath)
|
||||
} else if (filePath.startsWith('@project://')) {
|
||||
// 对于@project://路径,使用当前工作目录作为基础路径
|
||||
// 对于@project://路径,使用ProjectProtocol解析
|
||||
const ProjectProtocol = require('../../resource/protocols/ProjectProtocol')
|
||||
const projectProtocol = new ProjectProtocol()
|
||||
const relativePath = filePath.replace('@project://', '')
|
||||
filePath = path.join(process.cwd(), relativePath)
|
||||
filePath = await projectProtocol.resolvePath(relativePath)
|
||||
}
|
||||
|
||||
// 读取角色文件内容
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const { getDirectoryService } = require('../../utils/DirectoryService')
|
||||
|
||||
class ProtocolResolver {
|
||||
constructor() {
|
||||
this.packageRoot = null
|
||||
this.__dirname = __dirname
|
||||
this.directoryService = getDirectoryService()
|
||||
}
|
||||
|
||||
parseReference(reference) {
|
||||
@ -31,11 +33,11 @@ class ProtocolResolver {
|
||||
|
||||
switch (protocol) {
|
||||
case 'package':
|
||||
return this.resolvePackage(resourcePath)
|
||||
return await this.resolvePackage(resourcePath)
|
||||
case 'project':
|
||||
return this.resolveProject(resourcePath)
|
||||
return await this.resolveProject(resourcePath)
|
||||
case 'file':
|
||||
return this.resolveFile(resourcePath)
|
||||
return await this.resolveFile(resourcePath)
|
||||
default:
|
||||
throw new Error(`Unsupported protocol: ${protocol}`)
|
||||
}
|
||||
@ -48,12 +50,38 @@ class ProtocolResolver {
|
||||
return path.resolve(this.packageRoot, relativePath)
|
||||
}
|
||||
|
||||
resolveProject(relativePath) {
|
||||
async resolveProject(relativePath) {
|
||||
try {
|
||||
const context = {
|
||||
startDir: process.cwd(),
|
||||
platform: process.platform,
|
||||
avoidUserHome: true
|
||||
}
|
||||
const projectRoot = await this.directoryService.getProjectRoot(context)
|
||||
return path.resolve(projectRoot, relativePath)
|
||||
} catch (error) {
|
||||
// 回退到原始逻辑
|
||||
return path.resolve(process.cwd(), relativePath)
|
||||
}
|
||||
}
|
||||
|
||||
resolveFile(filePath) {
|
||||
return path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath)
|
||||
async resolveFile(filePath) {
|
||||
if (path.isAbsolute(filePath)) {
|
||||
return filePath
|
||||
}
|
||||
|
||||
try {
|
||||
const context = {
|
||||
startDir: process.cwd(),
|
||||
platform: process.platform,
|
||||
avoidUserHome: true
|
||||
}
|
||||
const projectRoot = await this.directoryService.getProjectRoot(context)
|
||||
return path.resolve(projectRoot, filePath)
|
||||
} catch (error) {
|
||||
// 回退到原始逻辑
|
||||
return path.resolve(process.cwd(), filePath)
|
||||
}
|
||||
}
|
||||
|
||||
async findPackageRoot() {
|
||||
|
||||
@ -5,6 +5,7 @@ const logger = require('../../../utils/logger')
|
||||
const path = require('path')
|
||||
const fs = require('fs-extra')
|
||||
const CrossPlatformFileScanner = require('./CrossPlatformFileScanner')
|
||||
const { getDirectoryService } = require('../../../utils/DirectoryService')
|
||||
|
||||
/**
|
||||
* PackageDiscovery - 包级资源发现器
|
||||
@ -19,7 +20,9 @@ class PackageDiscovery extends BaseDiscovery {
|
||||
constructor() {
|
||||
super('PACKAGE', 1)
|
||||
this.fileScanner = new CrossPlatformFileScanner()
|
||||
this.registryPath = path.join(process.cwd(), 'src/package.registry.json')
|
||||
this.directoryService = getDirectoryService()
|
||||
// 将在_getRegistryPath()中动态计算
|
||||
this.registryPath = null
|
||||
}
|
||||
|
||||
/**
|
||||
@ -78,6 +81,29 @@ class PackageDiscovery extends BaseDiscovery {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取注册表路径
|
||||
* @returns {Promise<string>} 注册表文件路径
|
||||
* @private
|
||||
*/
|
||||
async _getRegistryPath() {
|
||||
if (!this.registryPath) {
|
||||
try {
|
||||
const context = {
|
||||
startDir: process.cwd(),
|
||||
platform: process.platform,
|
||||
avoidUserHome: true
|
||||
}
|
||||
const projectRoot = await this.directoryService.getProjectRoot(context)
|
||||
this.registryPath = path.join(projectRoot, 'src/package.registry.json')
|
||||
} catch (error) {
|
||||
// 回退到默认路径
|
||||
this.registryPath = path.join(process.cwd(), 'src/package.registry.json')
|
||||
}
|
||||
}
|
||||
return this.registryPath
|
||||
}
|
||||
|
||||
/**
|
||||
* 从硬编码注册表加载资源
|
||||
* @returns {Promise<RegistryData|null>} 注册表数据
|
||||
@ -85,14 +111,15 @@ class PackageDiscovery extends BaseDiscovery {
|
||||
*/
|
||||
async _loadFromRegistry() {
|
||||
try {
|
||||
logger.debug(`[PackageDiscovery] 🔧 注册表路径: ${this.registryPath}`)
|
||||
const registryPath = await this._getRegistryPath()
|
||||
logger.debug(`[PackageDiscovery] 🔧 注册表路径: ${registryPath}`)
|
||||
|
||||
if (!(await fs.pathExists(this.registryPath))) {
|
||||
logger.warn(`[PackageDiscovery] ❌ 注册表文件不存在: ${this.registryPath}`)
|
||||
if (!(await fs.pathExists(registryPath))) {
|
||||
logger.warn(`[PackageDiscovery] ❌ 注册表文件不存在: ${registryPath}`)
|
||||
return null
|
||||
}
|
||||
|
||||
const registryData = await RegistryData.fromFile('package', this.registryPath)
|
||||
const registryData = await RegistryData.fromFile('package', registryPath)
|
||||
logger.debug(`[PackageDiscovery] 📊 加载资源总数: ${registryData.size}`)
|
||||
|
||||
return registryData
|
||||
@ -461,16 +488,22 @@ class PackageDiscovery extends BaseDiscovery {
|
||||
* @returns {Promise<boolean>} 是否为开发模式
|
||||
*/
|
||||
async _isDevelopmentMode() {
|
||||
const cwd = process.cwd()
|
||||
const hasCliScript = await fs.pathExists(path.join(cwd, 'src', 'bin', 'promptx.js'))
|
||||
const hasPackageJson = await fs.pathExists(path.join(cwd, 'package.json'))
|
||||
try {
|
||||
const context = {
|
||||
startDir: process.cwd(),
|
||||
platform: process.platform,
|
||||
avoidUserHome: true
|
||||
}
|
||||
const projectRoot = await this.directoryService.getProjectRoot(context)
|
||||
|
||||
const hasCliScript = await fs.pathExists(path.join(projectRoot, 'src', 'bin', 'promptx.js'))
|
||||
const hasPackageJson = await fs.pathExists(path.join(projectRoot, 'package.json'))
|
||||
|
||||
if (!hasCliScript || !hasPackageJson) {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
const packageJson = await fs.readJSON(path.join(cwd, 'package.json'))
|
||||
const packageJson = await fs.readJSON(path.join(projectRoot, 'package.json'))
|
||||
return packageJson.name === 'dpml-prompt'
|
||||
} catch (error) {
|
||||
return false
|
||||
|
||||
@ -4,6 +4,7 @@ const fsPromises = require('fs').promises
|
||||
const ResourceProtocol = require('./ResourceProtocol')
|
||||
const { QueryParams } = require('../types')
|
||||
const logger = require('../../../utils/logger')
|
||||
const { getDirectoryService } = require('../../../utils/DirectoryService')
|
||||
|
||||
/**
|
||||
* 包协议实现
|
||||
@ -16,6 +17,7 @@ class PackageProtocol extends ResourceProtocol {
|
||||
|
||||
// 包安装模式检测缓存
|
||||
this.installModeCache = new Map()
|
||||
this.directoryService = getDirectoryService()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -54,13 +56,13 @@ class PackageProtocol extends ResourceProtocol {
|
||||
/**
|
||||
* 检测当前包安装模式
|
||||
*/
|
||||
detectInstallMode () {
|
||||
async detectInstallMode () {
|
||||
const cacheKey = 'currentInstallMode'
|
||||
if (this.installModeCache.has(cacheKey)) {
|
||||
return this.installModeCache.get(cacheKey)
|
||||
}
|
||||
|
||||
const mode = this._performInstallModeDetection()
|
||||
const mode = await this._performInstallModeDetection()
|
||||
this.installModeCache.set(cacheKey, mode)
|
||||
return mode
|
||||
}
|
||||
@ -68,8 +70,19 @@ class PackageProtocol extends ResourceProtocol {
|
||||
/**
|
||||
* 执行安装模式检测
|
||||
*/
|
||||
_performInstallModeDetection () {
|
||||
const cwd = process.cwd()
|
||||
async _performInstallModeDetection () {
|
||||
let cwd
|
||||
try {
|
||||
const context = {
|
||||
startDir: process.cwd(),
|
||||
platform: process.platform,
|
||||
avoidUserHome: true
|
||||
}
|
||||
cwd = await this.directoryService.getProjectRoot(context)
|
||||
} catch (error) {
|
||||
cwd = process.cwd()
|
||||
}
|
||||
|
||||
const execPath = process.argv[0]
|
||||
const scriptPath = process.argv[1]
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
const ResourceProtocol = require('./ResourceProtocol')
|
||||
const path = require('path')
|
||||
const fs = require('fs').promises
|
||||
const { getDirectoryService } = require('../../../utils/DirectoryService')
|
||||
|
||||
/**
|
||||
* 项目协议实现
|
||||
@ -31,8 +32,8 @@ class ProjectProtocol extends ResourceProtocol {
|
||||
tools: 'tools' // 工具目录
|
||||
}
|
||||
|
||||
// 项目根目录缓存
|
||||
this.projectRootCache = new Map()
|
||||
// 获取全局DirectoryService实例
|
||||
this.directoryService = getDirectoryService()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -140,25 +141,15 @@ class ProjectProtocol extends ResourceProtocol {
|
||||
* @returns {Promise<string|null>} 项目根目录路径
|
||||
*/
|
||||
async findProjectRoot (startDir = process.cwd()) {
|
||||
// 检查缓存
|
||||
const cacheKey = path.resolve(startDir)
|
||||
if (this.projectRootCache.has(cacheKey)) {
|
||||
return this.projectRootCache.get(cacheKey)
|
||||
}
|
||||
|
||||
try {
|
||||
// 使用自实现的向上查找
|
||||
const promptxPath = this.findUpDirectorySync('.promptx', startDir)
|
||||
|
||||
let projectRoot = null
|
||||
if (promptxPath) {
|
||||
// .promptx 目录的父目录就是项目根目录
|
||||
projectRoot = path.dirname(promptxPath)
|
||||
// 使用DirectoryService获取项目根目录
|
||||
const context = {
|
||||
startDir: path.resolve(startDir),
|
||||
platform: process.platform,
|
||||
avoidUserHome: true
|
||||
}
|
||||
|
||||
// 缓存结果
|
||||
this.projectRootCache.set(cacheKey, projectRoot)
|
||||
|
||||
const projectRoot = await this.directoryService.getProjectRoot(context)
|
||||
return projectRoot
|
||||
} catch (error) {
|
||||
throw new Error(`查找项目根目录失败: ${error.message}`)
|
||||
@ -383,7 +374,8 @@ class ProjectProtocol extends ResourceProtocol {
|
||||
*/
|
||||
clearCache () {
|
||||
super.clearCache()
|
||||
this.projectRootCache.clear()
|
||||
// 清除DirectoryService缓存
|
||||
this.directoryService.clearCache()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -11,6 +11,9 @@ const { getDirectoryService } = require('./DirectoryService');
|
||||
* 保持向后兼容的API,但内部使用新的架构
|
||||
*
|
||||
* @deprecated 推荐直接使用 DirectoryService
|
||||
*
|
||||
* 注意:此文件主要保留向后兼容的同步API
|
||||
* 新代码请直接使用 DirectoryService 的异步API
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
@ -1,21 +1,43 @@
|
||||
const fs = require('fs-extra')
|
||||
const path = require('path')
|
||||
const { getDirectoryService } = require('./DirectoryService')
|
||||
|
||||
/**
|
||||
* PromptX配置文件管理工具
|
||||
* 统一管理.promptx目录下的所有配置文件
|
||||
*/
|
||||
class PromptXConfig {
|
||||
constructor(baseDir = process.cwd()) {
|
||||
constructor(baseDir = null) {
|
||||
this.baseDir = baseDir
|
||||
this.promptxDir = path.join(baseDir, '.promptx')
|
||||
this.directoryService = getDirectoryService()
|
||||
this.promptxDir = null // 将在需要时动态计算
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取.promptx目录路径
|
||||
*/
|
||||
async getPromptXDir() {
|
||||
if (!this.promptxDir) {
|
||||
if (this.baseDir) {
|
||||
this.promptxDir = path.join(this.baseDir, '.promptx')
|
||||
} else {
|
||||
const context = {
|
||||
startDir: process.cwd(),
|
||||
platform: process.platform,
|
||||
avoidUserHome: true
|
||||
}
|
||||
this.promptxDir = await this.directoryService.getPromptXDirectory(context)
|
||||
}
|
||||
}
|
||||
return this.promptxDir
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保.promptx目录存在
|
||||
*/
|
||||
async ensureDir() {
|
||||
await fs.ensureDir(this.promptxDir)
|
||||
const promptxDir = await this.getPromptXDir()
|
||||
await fs.ensureDir(promptxDir)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -25,7 +47,8 @@ class PromptXConfig {
|
||||
* @returns {Promise<*>} 配置对象
|
||||
*/
|
||||
async readJson(filename, defaultValue = {}) {
|
||||
const filePath = path.join(this.promptxDir, filename)
|
||||
const promptxDir = await this.getPromptXDir()
|
||||
const filePath = path.join(promptxDir, filename)
|
||||
try {
|
||||
if (await fs.pathExists(filePath)) {
|
||||
return await fs.readJson(filePath)
|
||||
@ -45,7 +68,8 @@ class PromptXConfig {
|
||||
*/
|
||||
async writeJson(filename, data, options = { spaces: 2 }) {
|
||||
await this.ensureDir()
|
||||
const filePath = path.join(this.promptxDir, filename)
|
||||
const promptxDir = await this.getPromptXDir()
|
||||
const filePath = path.join(promptxDir, filename)
|
||||
await fs.writeJson(filePath, data, options)
|
||||
}
|
||||
|
||||
|
||||
@ -88,10 +88,16 @@ describe('ProjectProtocol', () => {
|
||||
})
|
||||
|
||||
test('应该处理未找到项目根目录的情况', async () => {
|
||||
// 使用系统临时目录测试
|
||||
const tempDir = '/tmp'
|
||||
// 使用一个非常深的临时目录路径,确保不会找到项目标识
|
||||
const tempDir = '/tmp/very/deep/path/that/should/not/exist'
|
||||
try {
|
||||
const root = await projectProtocol.findProjectRoot(tempDir)
|
||||
expect(root).toBeNull()
|
||||
// DirectoryService 可能会返回一个回退值而不是null
|
||||
expect(typeof root).toBe('string')
|
||||
} catch (error) {
|
||||
// 如果找不到项目根目录,可能会抛出错误
|
||||
expect(error.message).toContain('查找项目根目录失败')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@ -246,10 +252,12 @@ describe('ProjectProtocol', () => {
|
||||
|
||||
test('应该能清除缓存', async () => {
|
||||
await projectProtocol.findProjectRoot() // 填充缓存
|
||||
expect(projectProtocol.projectRootCache.size).toBeGreaterThan(0)
|
||||
|
||||
// 现在使用DirectoryService的缓存,不是直接的projectRootCache
|
||||
projectProtocol.clearCache()
|
||||
expect(projectProtocol.projectRootCache.size).toBe(0)
|
||||
|
||||
// 验证清除操作不会抛出错误
|
||||
expect(() => projectProtocol.clearCache()).not.toThrow()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user