import { app } from "electron"; import fs from "node:fs"; import path from "node:path"; import type { Catalog, CatalogComponent } from "../shared/types"; let cached: Catalog | null = null; let excludedIds: Set | null = null; let duplicateMap: Map | null = null; function resolveCatalogPath(): string { const candidates = [ process.env.RPA_CATALOG_PATH, path.join(process.resourcesPath, "catalog.json"), path.join(app.getAppPath(), "resources", "catalog.json"), path.join(__dirname, "../../resources/catalog.json") ].filter(Boolean) as string[]; for (const candidate of candidates) { if (fs.existsSync(candidate)) return candidate; } throw new Error(`catalog.json not found; tried: ${candidates.join(", ")}`); } function duplicatesPath(): string { const candidates = [ process.env.RPA_DUPLICATE_PATH, path.join(process.resourcesPath, "capability-duplicates.json"), path.join(app.getAppPath(), "resources", "capability-duplicates.json"), path.join(__dirname, "../../resources/capability-duplicates.json") ].filter(Boolean) as string[]; return candidates.find((candidate) => fs.existsSync(candidate)) || ""; } function loadDuplicateMap(): Map { const map = new Map(); const file = duplicatesPath(); if (!file) return map; try { const parsed = JSON.parse(fs.readFileSync(file, "utf8")) as { decisions?: Array<{ id?: string; status?: string; canonical?: string }> }; for (const decision of parsed.decisions || []) { if (decision.id && decision.status === "hide" && decision.canonical) map.set(decision.id, decision.canonical); } } catch { // 审计文件损坏时保留完整目录 } return map; } function excludedPath(): string { const candidates = [ process.env.RPA_EXCLUDED_PATH, path.join(process.resourcesPath, "excluded.json"), path.join(app.getAppPath(), "resources", "excluded.json"), path.join(__dirname, "../../resources/excluded.json") ].filter(Boolean) as string[]; const found = candidates.find((candidate) => fs.existsSync(candidate)); return found || ""; } function loadExcludedIds(): Set { if (excludedIds) return excludedIds; excludedIds = new Set(); const file = excludedPath(); if (!file) return excludedIds; try { const parsed = JSON.parse(fs.readFileSync(file, "utf8")) as { ids?: string[] }; for (const id of parsed.ids || []) excludedIds.add(id); } catch { // 备份文件损坏时不阻塞目录加载 } return excludedIds; } export function catalogPath(): string { return resolveCatalogPath(); } export function loadCatalog(): Catalog { if (cached) return cached; const raw = fs.readFileSync(catalogPath(), "utf8"); const catalog = JSON.parse(raw) as Catalog; const excluded = loadExcludedIds(); const duplicates = duplicateMap || loadDuplicateMap(); duplicateMap = duplicates; catalog.components = catalog.components .filter((component) => !excluded.has(component.id)) .map((component: CatalogComponent) => ({ ...component, duplicateOf: duplicates.get(component.id) })); const visibleComponents = catalog.components.filter((component) => !component.duplicateOf); catalog.counts.total = visibleComponents.length; catalog.counts.vendor = visibleComponents.filter((component) => component.kind === "vendor").length; catalog.counts.native = visibleComponents.filter((component) => component.kind === "native").length; catalog.counts.triggers = visibleComponents.filter((component) => component.kind === "trigger").length; catalog.counts.fields = visibleComponents.reduce((sum, component) => sum + component.fields.length, 0); for (const group of catalog.groups) group.count = visibleComponents.filter((component) => component.group === group.id).length; cached = catalog; return catalog; } export function invalidateCatalog(): void { cached = null; }