# core/loaders/tree.loader.ts

> 147 lines of code and 35 definitions.

Tree: Bane's Lab Build Scripts
Language: typescript
Layer: infrastructure
Canonical: https://banes-lab.com/anatomy/build-scripts#file-build-scripts-core-loaders-tree-loader-ts
Source text: https://banes-lab.com/source/build-scripts/core/loaders/tree.loader.ts.txt

Listed in [core/loaders](https://banes-lab.com/api/source/build-scripts/core/loaders.md), after [core/loaders/surface.loader.ts](https://banes-lab.com/source/build-scripts/core/loaders/surface.loader.ts.md).

## Definitions

- `readDeclaredTree` (lexical_declaration, line 122, exported)
- `publishedIn` (lexical_declaration, line 131)
- `textIn` (lexical_declaration, line 41)
- `exportsIn` (lexical_declaration, line 46)
- `exportNameOf` (lexical_declaration, line 66)
- `declarationOf` (lexical_declaration, line 101)
- `publishedPaths` (lexical_declaration, line 138, exported)
- `manifestOf` (lexical_declaration, line 35)
- `file` (lexical_declaration, line 36)
- `parsed` (lexical_declaration, line 37)
- `value` (lexical_declaration, line 42)
- `map` (lexical_declaration, line 47)
- `overrides` (lexical_declaration, line 54)
- `tabOf` (lexical_declaration, line 56)
- `override` (lexical_declaration, line 57)
- `name` (lexical_declaration, line 61)
- `prefix` (lexical_declaration, line 62)
- `publishedKeyOf` (lexical_declaration, line 70, exported)
- `branch` (lexical_declaration, line 71, exported)
- `nested` (lexical_declaration, line 78, exported)
- `repositoryOf` (lexical_declaration, line 84)
- `key` (lexical_declaration, line 85)
- `root` (lexical_declaration, line 89)
- `TreeFacts` (interface_declaration, line 93)
- `{ folder, generatedSources, label, licenseFolder, tab }` (lexical_declaration, line 102)
- `repository` (lexical_declaration, line 103)
- `license` (lexical_declaration, line 104)
- `own` (lexical_declaration, line 108)
- `moduleDir` (lexical_declaration, line 126, exported)
- `shipped` (lexical_declaration, line 127, exported)
- `treeDeclarations` (lexical_declaration, line 145, exported)
- `members` (lexical_declaration, line 146, exported)
- `label` (lexical_declaration, line 147, exported)
- `configs` (lexical_declaration, line 152, exported)
- `site` (lexical_declaration, line 159, exported)

## Contained in

- [core/loaders](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-core-loaders.md)

## Uses

- [configuration/strings/anatomy.strings.ts](https://banes-lab.com/source/build-scripts/configuration/strings/anatomy.strings.ts.md)
- [core/loaders/structure.loader.ts](https://banes-lab.com/source/build-scripts/core/loaders/structure.loader.ts.md)
- [core/predicates/exclusion.predicate.ts](https://banes-lab.com/source/build-scripts/core/predicates/exclusion.predicate.ts.md)

## Linked from

- [configuration/strings](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-configuration-strings.md)
- [core/loaders](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-core-loaders.md)
- [core/predicates](https://banes-lab.com/anatomy/build-scripts/folder-build-scripts-core-predicates.md)

## Source

```typescript
import {
    CONFIG_TREE,
    EXPORT_JOIN,
    EXPORT_SUFFIX,
    FOLDER_SEPARATOR,
    INFRASTRUCTURE_BRANCH,
    MANIFEST_LABEL_KEY,
    METHODOLOGY_BRANCH,
    NAME_JOIN,
    PACKAGE_EXPORTS_KEY,
    PACKAGE_LICENSE_KEY,
    PACKAGE_NAME_KEY,
    SITE_EXPORT,
    TAB_JOIN,
    TAB_OVERRIDES,
    TREE_PREFIXES,
} from "#configuration/constants/tree.constants";
import { MODULE_MANIFEST_NAME, PACKAGE_MANIFEST_NAME } from "#configuration/constants/loader.constants";
import { ROOT, paths, relativePath } from "@ssot/paths";
import { exclusionOf, isPublished } from "#core/predicates/exclusion.predicate";
import { existsSync, readFileSync } from "node:fs";
import type { AnatomyTreeDeclaration } from "@banes-lab/web/types/anatomy.types.js";
import { CONFIG_TREE_LABEL } from "@banes-lab/web/strings/folder.strings";
import type { DiskFolder } from "#types/structure.types";
import type { PackageExport } from "@banes-lab/web/types/package.types.js";
import { REPOSITORY_TREE } from "@banes-lab/web/assets/link.assets";
import { TREE_TAB } from "@banes-lab/web/ids/anatomy.ids";
import { isRecord } from "#core/selectors/base.selector";
import { join } from "node:path";
import { readTree } from "#core/loaders/structure.loader";
import { shippedEntries } from "#core/loaders/coordination.loader";
import { unlicensedTree } from "#configuration/strings/anatomy.strings";
import { workspaceMembers } from "@govlab/docs";

const manifestOf = function manifestOf(folder: string, manifest: string): Readonly<Record<string, unknown>> {
    const file = join(ROOT, ...folder.split(FOLDER_SEPARATOR), manifest);
    const parsed: unknown = existsSync(file) ? JSON.parse(readFileSync(file, "utf8")) : null;
    return isRecord(parsed) ? parsed : {};
};

const textIn = function textIn(record: Readonly<Record<string, unknown>>, key: string): string | null {
    const value = record[key];
    return typeof value === "string" && value.length > 0 ? value : null;
};

const exportsIn = function exportsIn(record: Readonly<Record<string, unknown>>): readonly PackageExport[] {
    const map = record[PACKAGE_EXPORTS_KEY];
    if (!isRecord(map)) {
        return [];
    }
    return Object.entries(map).flatMap(([key, target]) => (typeof target === "string" ? [{ key, target }] : []));
};

const overrides = new Map(Object.entries(TAB_OVERRIDES).map(([key, tab]) => [relativePath(key), tab]));

const tabOf = function tabOf(folder: string): string {
    const override = overrides.get(folder);
    if (override !== undefined) {
        return override;
    }
    const name = folder.slice(folder.lastIndexOf(FOLDER_SEPARATOR) + 1);
    const prefix = TREE_PREFIXES.find((candidate) => name.startsWith(candidate)) ?? "";
    return name.slice(prefix.length).split(NAME_JOIN).join(TAB_JOIN);
};

const exportNameOf = function exportNameOf(tab: string): string {
    return tab === TREE_TAB ? SITE_EXPORT : tab.toUpperCase().split(TAB_JOIN).join(EXPORT_JOIN) + EXPORT_SUFFIX;
};

export const publishedKeyOf = function publishedKeyOf(tab: string): string | null {
    const branch = paths[METHODOLOGY_BRANCH];
    if (!isRecord(branch)) {
        return null;
    }
    if (typeof branch[tab] === "string") {
        return `${METHODOLOGY_BRANCH}.${tab}`;
    }
    const nested = branch[INFRASTRUCTURE_BRANCH];
    return isRecord(nested) && typeof nested[tab] === "string"
        ? `${METHODOLOGY_BRANCH}.${INFRASTRUCTURE_BRANCH}.${tab}`
        : null;
};

const repositoryOf = function repositoryOf(tab: string): string | null {
    const key = publishedKeyOf(tab);
    if (key === null) {
        return null;
    }
    const root = relativePath(METHODOLOGY_BRANCH);
    return REPOSITORY_TREE + relativePath(key).slice(root.length + FOLDER_SEPARATOR.length);
};

interface TreeFacts {
    readonly folder: string;
    readonly generatedSources: boolean;
    readonly label: string;
    readonly licenseFolder: string;
    readonly tab: string;
}

const declarationOf = function declarationOf(facts: TreeFacts): AnatomyTreeDeclaration {
    const { folder, generatedSources, label, licenseFolder, tab } = facts;
    const repository = repositoryOf(tab);
    const license = textIn(manifestOf(licenseFolder, PACKAGE_MANIFEST_NAME), PACKAGE_LICENSE_KEY);
    if (repository !== null && license === null) {
        throw new Error(unlicensedTree(tab, licenseFolder));
    }
    const own = manifestOf(folder, PACKAGE_MANIFEST_NAME);
    return {
        exportName: exportNameOf(tab),
        exports: exportsIn(own),
        folder,
        generatedSources,
        label,
        license,
        packageName: textIn(own, PACKAGE_NAME_KEY),
        repository,
        tab,
    };
};

export const readDeclaredTree = function readDeclaredTree(
    declaration: AnatomyTreeDeclaration,
    pruned: (folder: string) => boolean,
): DiskFolder {
    const moduleDir = join(ROOT, declaration.folder);
    const shipped = new Set(shippedEntries(moduleDir));
    return readTree({ excluded: exclusionOf, moduleDir, pruned, root: declaration.folder, shipped });
};

const publishedIn = function publishedIn(folder: DiskFolder, generatedSources: boolean): readonly string[] {
    return [
        ...folder.files.filter((file) => isPublished(file, generatedSources)).map((file) => file.path),
        ...folder.folders.flatMap((child) => publishedIn(child, generatedSources)),
    ];
};

export const publishedPaths = function publishedPaths(
    declaration: AnatomyTreeDeclaration,
    pruned: (folder: string) => boolean,
): readonly string[] {
    return publishedIn(readDeclaredTree(declaration, pruned), declaration.generatedSources);
};

export const treeDeclarations = function treeDeclarations(): readonly AnatomyTreeDeclaration[] {
    const members = workspaceMembers(ROOT).flatMap((folder) => {
        const label = textIn(manifestOf(folder, MODULE_MANIFEST_NAME), MANIFEST_LABEL_KEY);
        return label === null
            ? []
            : [declarationOf({ folder, generatedSources: false, label, licenseFolder: folder, tab: tabOf(folder) })];
    });
    const configs = declarationOf({
        folder: relativePath(CONFIG_TREE.key),
        generatedSources: CONFIG_TREE.generatedSources,
        label: CONFIG_TREE_LABEL,
        licenseFolder: relativePath(CONFIG_TREE.licenseKey),
        tab: CONFIG_TREE.tab,
    });
    const site = members.filter((declaration) => declaration.tab === TREE_TAB);
    return [...site, ...members.filter((declaration) => declaration.tab !== TREE_TAB), configs];
};
```
