core/loaders/icons.loader.ts

core/loaders/icons.loader.ts is a file in Bane's Lab Build Scripts. 66 lines of code and 14 definitions.

import {
    EVEN_ODD,
    ICONS_CONCERN,
    ICONS_SUFFIX,
    ICON_PREFIX,
    PATH_DATA_CLOSE,
    PATH_DATA_OPEN,
    PATH_OPEN,
    TAG_END,
} from "#configuration/constants/icons.constants";
import { absolutePath, relativePath } from "@ssot/paths";
import { readFileSync, readdirSync } from "node:fs";
import type { IconPath } from "@banes-lab/web/types/learning.types.js";
import { concernFolders } from "@ssot/govlab/shared/resolvers/container.resolver.ts";
import { iconSource } from "#core/resolvers/icons.resolver";
import { join } from "node:path";
import { pathToFileURL } from "node:url";
import { pathlessIcon } from "#configuration/strings/icons.strings";
import { projectDirs } from "@ssot/govlab/shared/resolvers/anchor.resolver.ts";

const MEMBER = relativePath("app.member");
const MEMBER_BELOW_ROOT = `${MEMBER.slice(MEMBER.indexOf("/") + 1)}/`;

const iconFiles = function iconFiles(): string[] {
    return concernFolders(ICONS_CONCERN, projectDirs())
        .filter((folder) => folder.startsWith(MEMBER_BELOW_ROOT))
        .map((folder) => absolutePath("app.root", folder))
        .flatMap((folder) =>
            readdirSync(folder)
                .filter((name) => name.endsWith(ICONS_SUFFIX))
                .sort((a, b) => a.localeCompare(b))
                .map((name) => join(folder, name)),
        );
};

const loadModule = async function loadModule(file: string): Promise<unknown> {
    const loaded: unknown = await import(pathToFileURL(file).href);
    return loaded;
};

const iconNamesOf = function iconNamesOf(module: unknown): string[] {
    if (typeof module !== "object" || module === null) {
        return [];
    }
    return Object.values(module)
        .filter((value): value is string => typeof value === "string" && value.startsWith(ICON_PREFIX))
        .map((value) => value.slice(ICON_PREFIX.length));
};

const pathOf = function pathOf(tag: string): IconPath {
    const start = tag.indexOf(PATH_DATA_OPEN) + PATH_DATA_OPEN.length;
    return { d: tag.slice(start, tag.indexOf(PATH_DATA_CLOSE, start)), evenOdd: tag.includes(EVEN_ODD) };
};

export const iconPathsOf = function iconPathsOf(name: string): readonly IconPath[] {
    const svg = readFileSync(iconSource(name), "utf8");
    const paths: IconPath[] = [];
    let at = svg.indexOf(PATH_OPEN);
    while (at !== -1) {
        const end = svg.indexOf(TAG_END, at);
        paths.push(pathOf(svg.slice(at, end)));
        at = svg.indexOf(PATH_OPEN, end);
    }
    if (paths.length === 0) {
        throw new TypeError(pathlessIcon(ICON_PREFIX + name));
    }
    return paths;
};

export const discoverIcons = async function discoverIcons(): Promise<readonly string[]> {
    const loaded = await Promise.all(iconFiles().map(loadModule));
    return [...new Set(loaded.flatMap(iconNamesOf))].sort((a, b) => a.localeCompare(b));
};