删除不再使用的bootstrap.md文件,更新promptx.js、MCPStreamableHttpCommand.js等文件以使用logger进行日志记录,重构资源管理和发现逻辑,确保代码一致性和可维护性。
This commit is contained in:
325
src/lib/core/resource/RegistryData.js
Normal file
325
src/lib/core/resource/RegistryData.js
Normal file
@ -0,0 +1,325 @@
|
||||
const fs = require('fs-extra')
|
||||
const path = require('path')
|
||||
const ResourceData = require('./ResourceData')
|
||||
|
||||
/**
|
||||
* 注册表数据管理器 v2.0
|
||||
* 基于ResourceData数组的全新架构,严格区分资源来源(source)和资源种类(protocol)
|
||||
*/
|
||||
class RegistryData {
|
||||
/**
|
||||
* @param {string} source - 注册表来源 ('package' | 'project' | 'user')
|
||||
* @param {string} filePath - 注册表文件路径
|
||||
* @param {Array<ResourceData>} resources - 资源数据数组
|
||||
* @param {Object} metadata - 注册表元数据
|
||||
*/
|
||||
constructor(source, filePath, resources = [], metadata = {}) {
|
||||
this.source = source
|
||||
this.filePath = filePath
|
||||
this.resources = resources.map(r => r instanceof ResourceData ? r : ResourceData.fromRawData(r))
|
||||
this.metadata = {
|
||||
version: "2.0.0",
|
||||
description: `${source} 级资源注册表`,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
...metadata
|
||||
}
|
||||
this.cache = new Map()
|
||||
}
|
||||
|
||||
/**
|
||||
* 从文件加载注册表数据
|
||||
* @param {string} source - 注册表来源
|
||||
* @param {string} filePath - 文件路径
|
||||
* @returns {Promise<RegistryData>} 注册表数据实例
|
||||
*/
|
||||
static async fromFile(source, filePath) {
|
||||
try {
|
||||
const data = await fs.readJSON(filePath)
|
||||
|
||||
// 处理新格式(v2.0)
|
||||
if (data.version === "2.0.0" && Array.isArray(data.resources)) {
|
||||
return new RegistryData(source, filePath, data.resources, data.metadata)
|
||||
}
|
||||
|
||||
// 处理旧格式(v1.0)- 自动转换
|
||||
if (data.resources && typeof data.resources === 'object') {
|
||||
const resources = []
|
||||
for (const [protocol, resourcesOfType] of Object.entries(data.resources)) {
|
||||
if (resourcesOfType && typeof resourcesOfType === 'object') {
|
||||
for (const [id, reference] of Object.entries(resourcesOfType)) {
|
||||
resources.push(ResourceData.fromFilePath(
|
||||
reference.replace(/^@\w+:\/\//, ''),
|
||||
source,
|
||||
protocol,
|
||||
reference
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
return new RegistryData(source, filePath, resources, {
|
||||
migratedFrom: "v1.0.0",
|
||||
originalTimestamp: data.timestamp
|
||||
})
|
||||
}
|
||||
|
||||
throw new Error(`Unsupported registry format in ${filePath}`)
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to load ${source} registry from ${filePath}: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建空的注册表数据
|
||||
* @param {string} source - 注册表来源
|
||||
* @param {string} filePath - 注册表文件路径
|
||||
* @returns {RegistryData} 空注册表数据实例
|
||||
*/
|
||||
static createEmpty(source, filePath) {
|
||||
return new RegistryData(source, filePath, [], {
|
||||
description: `${source} 级资源注册表`,
|
||||
createdAt: new Date().toISOString()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加资源
|
||||
* @param {ResourceData|Object} resource - 资源数据
|
||||
*/
|
||||
addResource(resource) {
|
||||
const resourceData = resource instanceof ResourceData ? resource : ResourceData.fromRawData(resource)
|
||||
|
||||
// 对于merged类型的注册表,保持原始来源信息
|
||||
// 只有在非merged注册表中才强制统一来源
|
||||
if (this.source !== 'merged' && resourceData.source !== this.source) {
|
||||
resourceData.source = this.source
|
||||
}
|
||||
|
||||
// 检查是否已存在相同ID的资源
|
||||
const existingIndex = this.resources.findIndex(r => r.id === resourceData.id && r.protocol === resourceData.protocol)
|
||||
|
||||
if (existingIndex >= 0) {
|
||||
// 更新现有资源
|
||||
this.resources[existingIndex] = resourceData
|
||||
} else {
|
||||
// 添加新资源
|
||||
this.resources.push(resourceData)
|
||||
}
|
||||
|
||||
this._updateMetadata()
|
||||
this.cache.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除资源
|
||||
* @param {string} id - 资源ID
|
||||
* @param {string} protocol - 资源协议
|
||||
* @returns {boolean} 是否成功移除
|
||||
*/
|
||||
removeResource(id, protocol) {
|
||||
const initialLength = this.resources.length
|
||||
this.resources = this.resources.filter(r => !(r.id === id && r.protocol === protocol))
|
||||
|
||||
const removed = this.resources.length < initialLength
|
||||
if (removed) {
|
||||
this._updateMetadata()
|
||||
this.cache.clear()
|
||||
}
|
||||
|
||||
return removed
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找资源
|
||||
* @param {Object} filters - 过滤条件
|
||||
* @returns {Array<ResourceData>} 匹配的资源数组
|
||||
*/
|
||||
findResources(filters = {}) {
|
||||
return this.resources.filter(resource => resource.matches(filters))
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID查找资源
|
||||
* @param {string} id - 资源ID
|
||||
* @param {string} protocol - 资源协议(可选)
|
||||
* @returns {ResourceData|null} 找到的资源
|
||||
*/
|
||||
findResourceById(id, protocol = null) {
|
||||
return this.resources.find(r => {
|
||||
if (protocol) {
|
||||
return r.id === id && r.protocol === protocol
|
||||
}
|
||||
return r.id === id
|
||||
}) || null
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定协议类型的所有资源
|
||||
* @param {string} protocol - 资源协议
|
||||
* @returns {Array<ResourceData>} 资源数组
|
||||
*/
|
||||
getResourcesByProtocol(protocol) {
|
||||
return this.resources.filter(r => r.protocol === protocol)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取资源Map(兼容旧接口)
|
||||
* @param {boolean} includeSourcePrefix - 是否包含源前缀
|
||||
* @returns {Map<string, string>} 资源ID到引用的映射
|
||||
*/
|
||||
getResourceMap(includeSourcePrefix = true) {
|
||||
const cacheKey = `resourceMap_${includeSourcePrefix}`
|
||||
if (this.cache.has(cacheKey)) {
|
||||
return this.cache.get(cacheKey)
|
||||
}
|
||||
|
||||
const registry = new Map()
|
||||
|
||||
for (const resource of this.resources) {
|
||||
if (includeSourcePrefix) {
|
||||
// 包含源前缀的完整ID
|
||||
registry.set(resource.getFullId(), resource.reference)
|
||||
// 同时也注册基础ID(用于向后兼容)
|
||||
registry.set(resource.getBaseId(), resource.reference)
|
||||
} else {
|
||||
// 仅使用基础ID
|
||||
registry.set(resource.getBaseId(), resource.reference)
|
||||
}
|
||||
}
|
||||
|
||||
this.cache.set(cacheKey, registry)
|
||||
return registry
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有资源数据
|
||||
* @returns {Array<ResourceData>} 所有资源数组
|
||||
*/
|
||||
getAllResources() {
|
||||
return [...this.resources]
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统计信息
|
||||
* @returns {Object} 统计信息
|
||||
*/
|
||||
getStats() {
|
||||
const stats = {
|
||||
totalResources: this.resources.length,
|
||||
byProtocol: {},
|
||||
bySource: {}
|
||||
}
|
||||
|
||||
for (const resource of this.resources) {
|
||||
// 按协议统计
|
||||
stats.byProtocol[resource.protocol] = (stats.byProtocol[resource.protocol] || 0) + 1
|
||||
|
||||
// 按来源统计
|
||||
stats.bySource[resource.source] = (stats.bySource[resource.source] || 0) + 1
|
||||
}
|
||||
|
||||
return stats
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并其他注册表数据
|
||||
* @param {RegistryData} otherRegistry - 其他注册表数据
|
||||
* @param {boolean} overwrite - 是否覆盖现有资源
|
||||
*/
|
||||
merge(otherRegistry, overwrite = false) {
|
||||
for (const resource of otherRegistry.resources) {
|
||||
const existing = this.findResourceById(resource.id, resource.protocol)
|
||||
|
||||
if (!existing || overwrite) {
|
||||
this.addResource(resource.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存注册表到文件
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async save() {
|
||||
try {
|
||||
// 确保目录存在
|
||||
await fs.ensureDir(path.dirname(this.filePath))
|
||||
|
||||
// 更新元数据
|
||||
this._updateMetadata()
|
||||
|
||||
// 构建保存数据
|
||||
const saveData = {
|
||||
version: this.metadata.version,
|
||||
source: this.source,
|
||||
metadata: this.metadata,
|
||||
resources: this.resources.map(r => r.toJSON()),
|
||||
stats: this.getStats()
|
||||
}
|
||||
|
||||
// 保存文件
|
||||
await fs.writeJSON(this.filePath, saveData, { spaces: 2 })
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to save ${this.source} registry to ${this.filePath}: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新元数据
|
||||
* @private
|
||||
*/
|
||||
_updateMetadata() {
|
||||
this.metadata.updatedAt = new Date().toISOString()
|
||||
this.metadata.resourceCount = this.resources.length
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取注册表大小
|
||||
* @returns {number} 资源数量
|
||||
*/
|
||||
get size() {
|
||||
return this.resources.length
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查注册表是否为空
|
||||
* @returns {boolean} 是否为空
|
||||
*/
|
||||
isEmpty() {
|
||||
return this.resources.length === 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有资源
|
||||
*/
|
||||
clear() {
|
||||
this.resources = []
|
||||
this._updateMetadata()
|
||||
this.cache.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* 克隆注册表数据
|
||||
* @returns {RegistryData} 克隆的注册表数据
|
||||
*/
|
||||
clone() {
|
||||
const clonedResources = this.resources.map(r => r.clone())
|
||||
return new RegistryData(this.source, this.filePath, clonedResources, { ...this.metadata })
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为JSON对象
|
||||
* @returns {Object} JSON对象
|
||||
*/
|
||||
toJSON() {
|
||||
return {
|
||||
version: this.metadata.version,
|
||||
source: this.source,
|
||||
metadata: this.metadata,
|
||||
resources: this.resources.map(r => r.toJSON()),
|
||||
stats: this.getStats()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RegistryData
|
||||
Reference in New Issue
Block a user