Files
Moltbot/src/config/includes-scan.ts
2026-02-17 09:18:40 +09:00

88 lines
2.2 KiB
TypeScript

import * as fs from "node:fs/promises";
import path from "node:path";
import JSON5 from "json5";
import { INCLUDE_KEY, MAX_INCLUDE_DEPTH } from "./includes.js";
function listDirectIncludes(parsed: unknown): string[] {
const out: string[] = [];
const visit = (value: unknown) => {
if (!value) {
return;
}
if (Array.isArray(value)) {
for (const item of value) {
visit(item);
}
return;
}
if (typeof value !== "object") {
return;
}
const rec = value as Record<string, unknown>;
const includeVal = rec[INCLUDE_KEY];
if (typeof includeVal === "string") {
out.push(includeVal);
} else if (Array.isArray(includeVal)) {
for (const item of includeVal) {
if (typeof item === "string") {
out.push(item);
}
}
}
for (const v of Object.values(rec)) {
visit(v);
}
};
visit(parsed);
return out;
}
function resolveIncludePath(baseConfigPath: string, includePath: string): string {
return path.normalize(
path.isAbsolute(includePath)
? includePath
: path.resolve(path.dirname(baseConfigPath), includePath),
);
}
export async function collectIncludePathsRecursive(params: {
configPath: string;
parsed: unknown;
}): Promise<string[]> {
const visited = new Set<string>();
const result: string[] = [];
const walk = async (basePath: string, parsed: unknown, depth: number): Promise<void> => {
if (depth > MAX_INCLUDE_DEPTH) {
return;
}
for (const raw of listDirectIncludes(parsed)) {
const resolved = resolveIncludePath(basePath, raw);
if (visited.has(resolved)) {
continue;
}
visited.add(resolved);
result.push(resolved);
const rawText = await fs.readFile(resolved, "utf-8").catch(() => null);
if (!rawText) {
continue;
}
const nestedParsed = (() => {
try {
return JSON5.parse(rawText);
} catch {
return null;
}
})();
if (nestedParsed) {
// eslint-disable-next-line no-await-in-loop
await walk(resolved, nestedParsed, depth + 1);
}
}
};
await walk(params.configPath, params.parsed, 0);
return result;
}