UNPKG

dpml-prompt

Version:

DPML-powered AI prompt framework - Revolutionary AI-First CLI system based on Deepractice Prompt Markup Language. Build sophisticated AI agents with structured prompts, memory systems, and execution frameworks.

187 lines (168 loc) 5.81 kB
const fs = require('fs-extra') const path = require('path') const logger = require('../../../utils/logger') /** * CrossPlatformFileScanner - 跨平台文件扫描器 * * 替代glob库,使用Node.js原生fs API实现跨平台文件扫描 * 避免glob在Windows上的兼容性问题 */ class CrossPlatformFileScanner { /** * 递归扫描目录,查找匹配的文件 * @param {string} baseDir - 基础目录 * @param {Object} options - 扫描选项 * @param {Array<string>} options.extensions - 文件扩展名列表,如 ['.role.md', '.execution.md'] * @param {Array<string>} options.subdirs - 限制扫描的子目录,如 ['domain', 'execution'] * @param {number} options.maxDepth - 最大扫描深度,默认5 * @returns {Promise<Array<string>>} 匹配的文件路径列表 */ async scanFiles(baseDir, options = {}) { const { extensions = [], subdirs = null, maxDepth = 5 } = options if (!await fs.pathExists(baseDir)) { return [] } const results = [] await this._scanRecursive(baseDir, baseDir, extensions, subdirs, maxDepth, 0, results) return results } /** * 扫描特定类型的资源文件 * @param {string} baseDir - 基础目录 * @param {string} resourceType - 资源类型 ('role', 'execution', 'thought') * @returns {Promise<Array<string>>} 匹配的文件路径列表 */ async scanResourceFiles(baseDir, resourceType) { const resourceConfig = { role: { extensions: ['.role.md'], subdirs: null // 不限制子目录,在所有地方查找role文件 }, execution: { extensions: ['.execution.md'], subdirs: null // 不限制子目录,在所有地方查找execution文件 }, thought: { extensions: ['.thought.md'], subdirs: null // 不限制子目录,在所有地方查找thought文件 }, knowledge: { extensions: ['.knowledge.md'], subdirs: null // 不限制子目录,在所有地方查找knowledge文件 }, tool: { extensions: ['.tool.js'], subdirs: null // 不限制子目录,在所有地方查找tool文件 } } const config = resourceConfig[resourceType] if (!config) { throw new Error(`Unsupported resource type: ${resourceType}`) } return await this.scanFiles(baseDir, config) } /** * 递归扫描目录的内部实现 * @param {string} currentDir - 当前扫描目录 * @param {string} baseDir - 基础目录 * @param {Array<string>} extensions - 文件扩展名列表 * @param {Array<string>|null} subdirs - 限制扫描的子目录 * @param {number} maxDepth - 最大深度 * @param {number} currentDepth - 当前深度 * @param {Array<string>} results - 结果数组 * @private */ async _scanRecursive(currentDir, baseDir, extensions, subdirs, maxDepth, currentDepth, results) { if (currentDepth >= maxDepth) { return } try { const entries = await fs.readdir(currentDir, { withFileTypes: true }) for (const entry of entries) { const fullPath = path.join(currentDir, entry.name) if (entry.isFile()) { // 检查文件扩展名 if (this._matchesExtensions(entry.name, extensions)) { results.push(fullPath) } } else if (entry.isDirectory()) { // 检查是否应该扫描这个子目录 if (this._shouldScanDirectory(entry.name, subdirs, currentDepth)) { await this._scanRecursive( fullPath, baseDir, extensions, subdirs, maxDepth, currentDepth + 1, results ) } } } } catch (error) { // 忽略权限错误或其他文件系统错误 logger.warn(`[CrossPlatformFileScanner] Failed to scan directory ${currentDir}: ${error.message}`) } } /** * 检查文件名是否匹配指定扩展名 * @param {string} fileName - 文件名 * @param {Array<string>} extensions - 扩展名列表 * @returns {boolean} 是否匹配 * @private */ _matchesExtensions(fileName, extensions) { if (!extensions || extensions.length === 0) { return true // 如果没有指定扩展名,匹配所有文件 } return extensions.some(ext => fileName.endsWith(ext)) } /** * 检查是否应该扫描指定目录 * @param {string} dirName - 目录名 * @param {Array<string>|null} subdirs - 允许扫描的子目录列表 * @param {number} currentDepth - 当前深度 * @returns {boolean} 是否应该扫描 * @private */ _shouldScanDirectory(dirName, subdirs, currentDepth) { // 跳过隐藏目录和node_modules if (dirName.startsWith('.') || dirName === 'node_modules') { return false } // 如果没有指定子目录限制,扫描所有目录 if (!subdirs || subdirs.length === 0) { return true } // 在根级别,只扫描指定的子目录 if (currentDepth === 0) { return subdirs.includes(dirName) } // 在更深层级,扫描所有目录 return true } /** * 规范化路径,确保跨平台兼容性 * @param {string} filePath - 文件路径 * @returns {string} 规范化后的路径 */ normalizePath(filePath) { return path.normalize(filePath).replace(/\\/g, '/') } /** * 生成相对路径,确保跨平台兼容性 * @param {string} from - 起始路径 * @param {string} to - 目标路径 * @returns {string} 规范化的相对路径 */ getRelativePath(from, to) { const relativePath = path.relative(from, to) return relativePath.replace(/\\/g, '/') } } module.exports = CrossPlatformFileScanner