Files
Zos/Skills/@be/be/menu/menu-entry-scanner.js

151 lines
4.3 KiB
JavaScript

"use strict";
const fs = require("fs");
const path = require("path");
function safeReadJson(filePath, logFn) {
try {
const txt = fs.readFileSync(filePath, "utf8");
if (!txt || txt.trim().length === 0) return null;
return JSON.parse(txt);
} catch (e) {
if (logFn) logFn("safeReadJson failed", filePath, e && e.message ? e.message : e);
return null;
}
}
function isDirectory(p) {
try {
return fs.statSync(p).isDirectory();
} catch (e) {
return false;
}
}
function scanSkillEntry(skillDir, folderName, opts) {
opts = opts || {};
const menuJsonPath = path.join(skillDir, "menuEntry.json");
const menuJson = safeReadJson(menuJsonPath, opts.log);
if (!menuJson) return null;
if (menuJson.hidden === true) return null;
const entry = {
id: folderName,
type: menuJson.type || "skill",
title: menuJson.title || menuJson.label || folderName,
icon: menuJson.icon || menuJson.iconSrc || (opts.defaultIcon || "resources/icons/settings.png"),
color: menuJson.color || menuJson.colors || (opts.defaultColor || "default"),
description: menuJson.description || "",
path: skillDir,
order: typeof menuJson.order === "number" ? menuJson.order : (typeof opts.defaultOrder === "number" ? opts.defaultOrder : 100),
skillId: menuJson.skillId || folderName,
submenuTitle: menuJson.submenuTitle || menuJson.title || folderName
};
if (entry.type === "submenu") {
entry.children = scanSubmenuChildren(skillDir, opts);
}
// Legacy support
if (menuJson.isSubmenu === true && entry.type !== "submenu") {
entry.type = "submenu";
entry.children = scanSubmenuChildren(skillDir, opts);
}
return entry;
}
function scanSubmenuChildren(submenuDir, opts) {
const children = [];
let items;
try {
items = fs.readdirSync(submenuDir);
} catch (e) {
return children;
}
items.forEach(function (name) {
if (!name || name.charAt(0) === ".") return;
if (name === "@be" || name === "node_modules") return;
const childPath = path.join(submenuDir, name);
if (!isDirectory(childPath)) return;
const entry = scanSkillEntry(childPath, name, opts);
if (entry && entry.type === "skill") {
children.push(entry);
}
});
children.sort(function (a, b) {
if (a.order !== b.order) return a.order - b.order;
return String(a.title || "").localeCompare(String(b.title || ""));
});
return children;
}
function scanAllMenuEntries(skillsRoot, opts) {
opts = opts || {};
const entries = [];
if (!skillsRoot || !fs.existsSync(skillsRoot)) {
if (opts.log) opts.log("skills root missing", skillsRoot);
return entries;
}
let children;
try {
children = fs.readdirSync(skillsRoot);
} catch (e) {
if (opts.log) opts.log("failed to read skills root", skillsRoot, e && e.message ? e.message : e);
return entries;
}
children.forEach(function (name) {
if (!name || name.charAt(0) === ".") return;
if (name === "@be" || name === "node_modules") return;
const skillDir = path.join(skillsRoot, name);
if (!isDirectory(skillDir)) return;
const entry = scanSkillEntry(skillDir, name, opts);
if (entry) entries.push(entry);
});
entries.sort(function (a, b) {
if (a.order !== b.order) return a.order - b.order;
return String(a.title || "").localeCompare(String(b.title || ""));
});
return entries;
}
function collectDynamicSkillIds(entries) {
const ids = new Set();
(entries || []).forEach(function (e) {
if (!e) return;
if (e.type === "skill") {
if (e.skillId) ids.add(e.skillId);
if (e.id) ids.add(e.id);
}
if (e.type === "submenu" && e.children && e.children.length) {
e.children.forEach(function (c) {
if (!c) return;
if (c.skillId) ids.add(c.skillId);
if (c.id) ids.add(c.id);
});
}
});
return ids;
}
module.exports = {
scanAllMenuEntries: scanAllMenuEntries,
scanSubmenuChildren: scanSubmenuChildren,
collectDynamicSkillIds: collectDynamicSkillIds
};