feat: 更新命令名称为dpml-prompt,保持PromptX品牌名称
This commit is contained in:
@ -1,53 +1,53 @@
|
||||
const ResourceProtocol = require('./ResourceProtocol');
|
||||
const path = require('path');
|
||||
const fs = require('fs').promises;
|
||||
const ResourceProtocol = require('./ResourceProtocol')
|
||||
const path = require('path')
|
||||
const fs = require('fs').promises
|
||||
|
||||
/**
|
||||
* 项目协议实现
|
||||
* 实现@project://协议,通过查找.promptx目录确定项目根目录
|
||||
*/
|
||||
class ProjectProtocol extends ResourceProtocol {
|
||||
constructor(options = {}) {
|
||||
super('project', options);
|
||||
|
||||
constructor (options = {}) {
|
||||
super('project', options)
|
||||
|
||||
// 支持的项目结构目录映射
|
||||
this.projectDirs = {
|
||||
'root': '', // 项目根目录
|
||||
'src': 'src', // 源代码目录
|
||||
'lib': 'lib', // 库目录
|
||||
'build': 'build', // 构建输出目录
|
||||
'dist': 'dist', // 分发目录
|
||||
'docs': 'docs', // 文档目录
|
||||
'test': 'test', // 测试目录
|
||||
'tests': 'tests', // 测试目录(复数)
|
||||
'spec': 'spec', // 规范测试目录
|
||||
'config': 'config', // 配置目录
|
||||
'scripts': 'scripts', // 脚本目录
|
||||
'assets': 'assets', // 资源目录
|
||||
'public': 'public', // 公共资源目录
|
||||
'static': 'static', // 静态资源目录
|
||||
'templates': 'templates', // 模板目录
|
||||
'examples': 'examples', // 示例目录
|
||||
'tools': 'tools' // 工具目录
|
||||
};
|
||||
|
||||
root: '', // 项目根目录
|
||||
src: 'src', // 源代码目录
|
||||
lib: 'lib', // 库目录
|
||||
build: 'build', // 构建输出目录
|
||||
dist: 'dist', // 分发目录
|
||||
docs: 'docs', // 文档目录
|
||||
test: 'test', // 测试目录
|
||||
tests: 'tests', // 测试目录(复数)
|
||||
spec: 'spec', // 规范测试目录
|
||||
config: 'config', // 配置目录
|
||||
scripts: 'scripts', // 脚本目录
|
||||
assets: 'assets', // 资源目录
|
||||
public: 'public', // 公共资源目录
|
||||
static: 'static', // 静态资源目录
|
||||
templates: 'templates', // 模板目录
|
||||
examples: 'examples', // 示例目录
|
||||
tools: 'tools' // 工具目录
|
||||
}
|
||||
|
||||
// 项目根目录缓存
|
||||
this.projectRootCache = new Map();
|
||||
this.projectRootCache = new Map()
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置注册表(保持与其他协议的一致性)
|
||||
*/
|
||||
setRegistry(registry) {
|
||||
setRegistry (registry) {
|
||||
// Project协议不使用注册表,但为了一致性提供此方法
|
||||
this.registry = registry || {};
|
||||
this.registry = registry || {}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取协议信息
|
||||
* @returns {object} 协议信息
|
||||
*/
|
||||
getProtocolInfo() {
|
||||
getProtocolInfo () {
|
||||
return {
|
||||
name: 'project',
|
||||
description: '项目协议,通过.promptx目录标识提供项目结构访问',
|
||||
@ -62,21 +62,21 @@ class ProjectProtocol extends ResourceProtocol {
|
||||
supportedDirectories: Object.keys(this.projectDirs),
|
||||
projectMarker: '.promptx',
|
||||
params: this.getSupportedParams()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 支持的查询参数
|
||||
* @returns {object} 参数说明
|
||||
*/
|
||||
getSupportedParams() {
|
||||
getSupportedParams () {
|
||||
return {
|
||||
...super.getSupportedParams(),
|
||||
from: 'string - 指定搜索起始目录',
|
||||
create: 'boolean - 如果目录不存在是否创建',
|
||||
exists: 'boolean - 仅返回存在的文件/目录',
|
||||
type: 'string - 过滤类型 (file|dir|both)'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -84,16 +84,16 @@ class ProjectProtocol extends ResourceProtocol {
|
||||
* @param {string} resourcePath - 资源路径
|
||||
* @returns {boolean} 是否有效
|
||||
*/
|
||||
validatePath(resourcePath) {
|
||||
validatePath (resourcePath) {
|
||||
if (!super.validatePath(resourcePath)) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
|
||||
// 解析路径的第一部分(目录类型)
|
||||
const parts = resourcePath.split('/');
|
||||
const dirType = parts[0];
|
||||
|
||||
return this.projectDirs.hasOwnProperty(dirType);
|
||||
const parts = resourcePath.split('/')
|
||||
const dirType = parts[0]
|
||||
|
||||
return this.projectDirs.hasOwnProperty(dirType)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -102,31 +102,31 @@ class ProjectProtocol extends ResourceProtocol {
|
||||
* @param {string} startDir - 开始搜索的目录
|
||||
* @returns {string|null} 找到的目录路径或null
|
||||
*/
|
||||
findUpDirectorySync(targetDir, startDir = process.cwd()) {
|
||||
let currentDir = path.resolve(startDir);
|
||||
const rootDir = path.parse(currentDir).root;
|
||||
findUpDirectorySync (targetDir, startDir = process.cwd()) {
|
||||
let currentDir = path.resolve(startDir)
|
||||
const rootDir = path.parse(currentDir).root
|
||||
|
||||
while (currentDir !== rootDir) {
|
||||
const targetPath = path.join(currentDir, targetDir);
|
||||
|
||||
const targetPath = path.join(currentDir, targetDir)
|
||||
|
||||
try {
|
||||
const stats = require('fs').statSync(targetPath);
|
||||
const stats = require('fs').statSync(targetPath)
|
||||
if (stats.isDirectory()) {
|
||||
return targetPath;
|
||||
return targetPath
|
||||
}
|
||||
} catch (error) {
|
||||
// 目录不存在,继续向上查找
|
||||
}
|
||||
|
||||
const parentDir = path.dirname(currentDir);
|
||||
const parentDir = path.dirname(currentDir)
|
||||
if (parentDir === currentDir) {
|
||||
// 已到达根目录
|
||||
break;
|
||||
break
|
||||
}
|
||||
currentDir = parentDir;
|
||||
currentDir = parentDir
|
||||
}
|
||||
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
@ -134,29 +134,29 @@ class ProjectProtocol extends ResourceProtocol {
|
||||
* @param {string} startDir - 开始搜索的目录
|
||||
* @returns {Promise<string|null>} 项目根目录路径
|
||||
*/
|
||||
async findProjectRoot(startDir = process.cwd()) {
|
||||
async findProjectRoot (startDir = process.cwd()) {
|
||||
// 检查缓存
|
||||
const cacheKey = path.resolve(startDir);
|
||||
const cacheKey = path.resolve(startDir)
|
||||
if (this.projectRootCache.has(cacheKey)) {
|
||||
return this.projectRootCache.get(cacheKey);
|
||||
return this.projectRootCache.get(cacheKey)
|
||||
}
|
||||
|
||||
try {
|
||||
// 使用自实现的向上查找
|
||||
const promptxPath = this.findUpDirectorySync('.promptx', startDir);
|
||||
const promptxPath = this.findUpDirectorySync('.promptx', startDir)
|
||||
|
||||
let projectRoot = null;
|
||||
let projectRoot = null
|
||||
if (promptxPath) {
|
||||
// .promptx 目录的父目录就是项目根目录
|
||||
projectRoot = path.dirname(promptxPath);
|
||||
projectRoot = path.dirname(promptxPath)
|
||||
}
|
||||
|
||||
// 缓存结果
|
||||
this.projectRootCache.set(cacheKey, projectRoot);
|
||||
|
||||
return projectRoot;
|
||||
this.projectRootCache.set(cacheKey, projectRoot)
|
||||
|
||||
return projectRoot
|
||||
} catch (error) {
|
||||
throw new Error(`查找项目根目录失败: ${error.message}`);
|
||||
throw new Error(`查找项目根目录失败: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
@ -166,46 +166,46 @@ class ProjectProtocol extends ResourceProtocol {
|
||||
* @param {QueryParams} queryParams - 查询参数
|
||||
* @returns {Promise<string>} 解析后的绝对路径
|
||||
*/
|
||||
async resolvePath(resourcePath, queryParams) {
|
||||
const parts = resourcePath.split('/');
|
||||
const dirType = parts[0];
|
||||
const relativePath = parts.slice(1).join('/');
|
||||
async resolvePath (resourcePath, queryParams) {
|
||||
const parts = resourcePath.split('/')
|
||||
const dirType = parts[0]
|
||||
const relativePath = parts.slice(1).join('/')
|
||||
|
||||
// 验证目录类型
|
||||
if (!this.projectDirs.hasOwnProperty(dirType)) {
|
||||
throw new Error(`不支持的项目目录类型: ${dirType}。支持的类型: ${Object.keys(this.projectDirs).join(', ')}`);
|
||||
throw new Error(`不支持的项目目录类型: ${dirType}。支持的类型: ${Object.keys(this.projectDirs).join(', ')}`)
|
||||
}
|
||||
|
||||
// 确定搜索起始点
|
||||
const startDir = queryParams?.get('from') || process.cwd();
|
||||
|
||||
const startDir = queryParams?.get('from') || process.cwd()
|
||||
|
||||
// 查找项目根目录
|
||||
const projectRoot = await this.findProjectRoot(startDir);
|
||||
const projectRoot = await this.findProjectRoot(startDir)
|
||||
if (!projectRoot) {
|
||||
throw new Error(`未找到项目根目录(.promptx标识)。请确保在项目目录内或使用 'from' 参数指定项目路径`);
|
||||
throw new Error('未找到项目根目录(.promptx标识)。请确保在项目目录内或使用 \'from\' 参数指定项目路径')
|
||||
}
|
||||
|
||||
// 构建目标目录路径
|
||||
const projectDirPath = this.projectDirs[dirType];
|
||||
const targetDir = projectDirPath ? path.join(projectRoot, projectDirPath) : projectRoot;
|
||||
|
||||
const projectDirPath = this.projectDirs[dirType]
|
||||
const targetDir = projectDirPath ? path.join(projectRoot, projectDirPath) : projectRoot
|
||||
|
||||
// 如果没有相对路径,返回目录本身
|
||||
if (!relativePath) {
|
||||
return targetDir;
|
||||
return targetDir
|
||||
}
|
||||
|
||||
// 拼接完整路径
|
||||
const fullPath = path.join(targetDir, relativePath);
|
||||
|
||||
const fullPath = path.join(targetDir, relativePath)
|
||||
|
||||
// 安全检查:确保路径在项目目录内
|
||||
const resolvedPath = path.resolve(fullPath);
|
||||
const resolvedProjectRoot = path.resolve(projectRoot);
|
||||
|
||||
const resolvedPath = path.resolve(fullPath)
|
||||
const resolvedProjectRoot = path.resolve(projectRoot)
|
||||
|
||||
if (!resolvedPath.startsWith(resolvedProjectRoot)) {
|
||||
throw new Error(`安全错误:路径超出项目目录范围: ${resolvedPath}`);
|
||||
throw new Error(`安全错误:路径超出项目目录范围: ${resolvedPath}`)
|
||||
}
|
||||
|
||||
return resolvedPath;
|
||||
return resolvedPath
|
||||
}
|
||||
|
||||
/**
|
||||
@ -214,33 +214,33 @@ class ProjectProtocol extends ResourceProtocol {
|
||||
* @param {QueryParams} queryParams - 查询参数
|
||||
* @returns {Promise<string>} 资源内容
|
||||
*/
|
||||
async loadContent(resolvedPath, queryParams) {
|
||||
async loadContent (resolvedPath, queryParams) {
|
||||
try {
|
||||
// 检查路径是否存在
|
||||
const stats = await fs.stat(resolvedPath);
|
||||
|
||||
const stats = await fs.stat(resolvedPath)
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
return await this.loadDirectoryContent(resolvedPath, queryParams);
|
||||
return await this.loadDirectoryContent(resolvedPath, queryParams)
|
||||
} else if (stats.isFile()) {
|
||||
return await this.loadFileContent(resolvedPath, queryParams);
|
||||
return await this.loadFileContent(resolvedPath, queryParams)
|
||||
} else {
|
||||
throw new Error(`不支持的文件类型: ${resolvedPath}`);
|
||||
throw new Error(`不支持的文件类型: ${resolvedPath}`)
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
// 检查是否需要创建目录
|
||||
if (queryParams?.get('create') === 'true') {
|
||||
await fs.mkdir(path.dirname(resolvedPath), { recursive: true });
|
||||
return ''; // 返回空内容
|
||||
await fs.mkdir(path.dirname(resolvedPath), { recursive: true })
|
||||
return '' // 返回空内容
|
||||
}
|
||||
|
||||
|
||||
// 如果设置了exists参数为false,返回空内容而不是错误
|
||||
if (queryParams?.get('exists') === 'false') {
|
||||
return '';
|
||||
return ''
|
||||
}
|
||||
throw new Error(`文件或目录不存在: ${resolvedPath}`);
|
||||
throw new Error(`文件或目录不存在: ${resolvedPath}`)
|
||||
}
|
||||
throw error;
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@ -250,9 +250,9 @@ class ProjectProtocol extends ResourceProtocol {
|
||||
* @param {QueryParams} queryParams - 查询参数
|
||||
* @returns {Promise<string>} 文件内容
|
||||
*/
|
||||
async loadFileContent(filePath, queryParams) {
|
||||
const encoding = queryParams?.get('encoding') || 'utf8';
|
||||
return await fs.readFile(filePath, encoding);
|
||||
async loadFileContent (filePath, queryParams) {
|
||||
const encoding = queryParams?.get('encoding') || 'utf8'
|
||||
return await fs.readFile(filePath, encoding)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -261,27 +261,27 @@ class ProjectProtocol extends ResourceProtocol {
|
||||
* @param {QueryParams} queryParams - 查询参数
|
||||
* @returns {Promise<string>} 目录内容列表
|
||||
*/
|
||||
async loadDirectoryContent(dirPath, queryParams) {
|
||||
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
||||
|
||||
async loadDirectoryContent (dirPath, queryParams) {
|
||||
const entries = await fs.readdir(dirPath, { withFileTypes: true })
|
||||
|
||||
// 应用类型过滤
|
||||
const typeFilter = queryParams?.get('type');
|
||||
let filteredEntries = entries;
|
||||
|
||||
const typeFilter = queryParams?.get('type')
|
||||
let filteredEntries = entries
|
||||
|
||||
if (typeFilter) {
|
||||
filteredEntries = entries.filter(entry => {
|
||||
switch (typeFilter) {
|
||||
case 'file': return entry.isFile();
|
||||
case 'dir': return entry.isDirectory();
|
||||
case 'both': return true;
|
||||
default: return true;
|
||||
case 'file': return entry.isFile()
|
||||
case 'dir': return entry.isDirectory()
|
||||
case 'both': return true
|
||||
default: return true
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
// 格式化输出
|
||||
const format = queryParams?.get('format') || 'list';
|
||||
|
||||
const format = queryParams?.get('format') || 'list'
|
||||
|
||||
switch (format) {
|
||||
case 'json':
|
||||
return JSON.stringify(
|
||||
@ -292,21 +292,21 @@ class ProjectProtocol extends ResourceProtocol {
|
||||
})),
|
||||
null,
|
||||
2
|
||||
);
|
||||
|
||||
)
|
||||
|
||||
case 'paths':
|
||||
return filteredEntries
|
||||
.map(entry => path.join(dirPath, entry.name))
|
||||
.join('\n');
|
||||
|
||||
.join('\n')
|
||||
|
||||
case 'list':
|
||||
default:
|
||||
return filteredEntries
|
||||
.map(entry => {
|
||||
const type = entry.isDirectory() ? '[DIR]' : '[FILE]';
|
||||
return `${type} ${entry.name}`;
|
||||
const type = entry.isDirectory() ? '[DIR]' : '[FILE]'
|
||||
return `${type} ${entry.name}`
|
||||
})
|
||||
.join('\n');
|
||||
.join('\n')
|
||||
}
|
||||
}
|
||||
|
||||
@ -315,45 +315,45 @@ class ProjectProtocol extends ResourceProtocol {
|
||||
* @param {string} startDir - 开始搜索的目录
|
||||
* @returns {Promise<object>} 项目信息
|
||||
*/
|
||||
async getProjectInfo(startDir = process.cwd()) {
|
||||
const projectRoot = await this.findProjectRoot(startDir);
|
||||
async getProjectInfo (startDir = process.cwd()) {
|
||||
const projectRoot = await this.findProjectRoot(startDir)
|
||||
if (!projectRoot) {
|
||||
return { error: '未找到项目根目录' };
|
||||
return { error: '未找到项目根目录' }
|
||||
}
|
||||
|
||||
const result = {
|
||||
projectRoot,
|
||||
promptxPath: path.join(projectRoot, '.promptx'),
|
||||
directories: {}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
for (const [dirType, dirPath] of Object.entries(this.projectDirs)) {
|
||||
const fullPath = dirPath ? path.join(projectRoot, dirPath) : projectRoot;
|
||||
const fullPath = dirPath ? path.join(projectRoot, dirPath) : projectRoot
|
||||
try {
|
||||
const stats = await fs.stat(fullPath);
|
||||
const stats = await fs.stat(fullPath)
|
||||
result.directories[dirType] = {
|
||||
path: fullPath,
|
||||
exists: true,
|
||||
type: stats.isDirectory() ? 'directory' : 'file'
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
result.directories[dirType] = {
|
||||
path: fullPath,
|
||||
exists: false
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除缓存
|
||||
*/
|
||||
clearCache() {
|
||||
super.clearCache();
|
||||
this.projectRootCache.clear();
|
||||
clearCache () {
|
||||
super.clearCache()
|
||||
this.projectRootCache.clear()
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ProjectProtocol;
|
||||
module.exports = ProjectProtocol
|
||||
|
||||
Reference in New Issue
Block a user