重构:引入DirectoryService以优化路径解析和项目根目录查找

- 在多个协议实现中(如ProjectProtocol、PackageProtocol等)引入DirectoryService,替换了直接的路径处理逻辑,增强了路径解析的智能性和可靠性。
- 更新了相关方法以支持异步操作,确保在查找项目根目录和注册表路径时能够优雅地处理错误并回退到默认路径。
- 在PromptXConfig中动态计算.promptx目录路径,提升了配置管理的灵活性。

此改动旨在提升代码的可读性和一致性,同时为未来的扩展打下基础。
This commit is contained in:
sean
2025-06-15 12:16:01 +08:00
parent 041ece9af1
commit d6a1f91722
9 changed files with 163 additions and 59 deletions

View File

@ -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) {
return path.resolve(process.cwd(), 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() {

View File

@ -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'))
if (!hasCliScript || !hasPackageJson) {
return false
}
try {
const packageJson = await fs.readJSON(path.join(cwd, 'package.json'))
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
}
const packageJson = await fs.readJSON(path.join(projectRoot, 'package.json'))
return packageJson.name === 'dpml-prompt'
} catch (error) {
return false

View File

@ -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]

View File

@ -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()
}
}