151 lines
4.3 KiB
JavaScript
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
|
|
};
|