# runtime/entrypoints/validation.entrypoint.ts

> 112 lines of code and 22 definitions.

Tree: Bane's Lab Social Share
Language: typescript
Layer: runtime
Canonical: https://banes-lab.com/anatomy/social-share#file-social-share-runtime-entrypoints-validation-entrypoint-ts
Source text: https://banes-lab.com/source/social-share/runtime/entrypoints/validation.entrypoint.ts.txt

Listed in [runtime/entrypoints](https://banes-lab.com/api/source/social-share/runtime/entrypoints.md), after [runtime/entrypoints/stage.entrypoint.ts](https://banes-lab.com/source/social-share/runtime/entrypoints/stage.entrypoint.ts.md).

## Definitions

- `mapFindings` (lexical_declaration, line 80)
- `isShareMapModule` (lexical_declaration, line 67)
- `outputFindings` (lexical_declaration, line 93)
- `heal` (lexical_declaration, line 106)
- `shareMap` (lexical_declaration, line 71)
- `ShareMapModule` (interface_declaration, line 39)
- `RENDERS` (lexical_declaration, line 45)
- `SHARES` (lexical_declaration, line 46)
- `FRESH_QUERY` (lexical_declaration, line 47)
- `SHARE_EXPORT` (lexical_declaration, line 48)
- `loader` (lexical_declaration, line 50)
- `loaded` (lexical_declaration, line 51)
- `specs` (lexical_declaration, line 54)
- `specFindings` (lexical_declaration, line 56)
- `location` (lexical_declaration, line 72)
- `module` (lexical_declaration, line 73)
- `expected` (lexical_declaration, line 81)
- `held` (lexical_declaration, line 82)
- `mapCurrent` (lexical_declaration, line 83)
- `missing` (lexical_declaration, line 94)
- `capture` (lexical_declaration, line 108)
- `result` (lexical_declaration, line 110)

## Contained in

- [runtime/entrypoints](https://banes-lab.com/anatomy/social-share/folder-social-share-runtime-entrypoints.md)

## Uses

- [core/persistence/export.persistence.ts](https://banes-lab.com/source/social-share/core/persistence/export.persistence.ts.md)
- [core/persistence/image.persistence.ts](https://banes-lab.com/source/social-share/core/persistence/image.persistence.ts.md)
- [core/reporters/card.reporter.ts](https://banes-lab.com/source/social-share/core/reporters/card.reporter.ts.md)

## Enforces

- [Reproducibility](https://banes-lab.com/records/architecture/reproducibility.md)

## Linked from

- [core/persistence](https://banes-lab.com/anatomy/social-share/folder-social-share-core-persistence.md)
- [core/reporters](https://banes-lab.com/anatomy/social-share/folder-social-share-core-reporters.md)
- [runtime/entrypoints](https://banes-lab.com/anatomy/social-share/folder-social-share-runtime-entrypoints.md)

## Source

```typescript
import {
    CARDS_VALID,
    HEALING,
    MALFORMED_SHARE_MAP,
    MISSING_IMAGE,
    NO_CARDS,
    ORPHAN_IMAGE,
    PRUNED_PREFIX,
    RENDERED_PREFIX,
    STALE_IMAGE,
    STALE_MAP,
    SUBJECT_SEPARATOR,
    VALIDATION_SUMMARY,
} from "#configuration/strings/card.strings";
import { conclude, say } from "#core/reporters/card.reporter";
import {
    missingImages,
    orphanImages,
    readLedger,
    shareEntriesOf,
    staleEntries,
} from "#core/persistence/image.persistence";
import { strayShares, unpublishedShares } from "#core/persistence/export.persistence";
import type { CardFinding } from "#types/card.types";
import { PROFILES } from "#configuration/configs/card.config";
import type { ShareImage } from "@banes-lab/web/types/card.types.ts";
import { VALIDATION_COMMAND } from "#configuration/constants/card.constants";
import { absolutePath } from "@ssot/paths";
import { animationFindings } from "#core/probes/image.probe";
import { defineCheck } from "@govlab/context/check";
import { exportCards } from "#core/coordinators/export.coordinator";
import { openStage } from "#core/adapters/stage.adapter";
import { pathToFileURL } from "node:url";
import { resolveArgv } from "@govlab/argv";
import { validateCards } from "#core/validators/card.validator";

defineCheck({ detects: [], enforces: ["architecture:reproducibility"] });

interface ShareMapModule {
    readonly SHARE_IMAGES: ReadonlyMap<string, ShareImage>;
}

resolveArgv({ command: VALIDATION_COMMAND, flags: [], summary: VALIDATION_SUMMARY });

const RENDERS = absolutePath("app.cardRenders");
const SHARES = absolutePath("app.shares");
const FRESH_QUERY = "?read=";
const SHARE_EXPORT = "SHARE_IMAGES";

const loader = await openStage(false);
const loaded = await loader.cards();
await loader.close();

const specs = loaded.cards.map((card) => card.spec);

const specFindings: readonly CardFinding[] = [
    ...(loaded.cards.length === 0 ? [{ card: VALIDATION_COMMAND, message: NO_CARDS }] : []),
    ...validateCards(loaded.cards, loaded.plugins, {
        brand: loaded.brand,
        pages: loaded.pages,
        profiles: PROFILES,
        repeated: loaded.repeated,
    }),
    ...(await animationFindings(specs, absolutePath("app.public"))),
];

const isShareMapModule = function isShareMapModule(value: unknown): value is ShareMapModule {
    return typeof value === "object" && value !== null && Reflect.get(value, SHARE_EXPORT) instanceof Map;
};

const shareMap = async function shareMap(): Promise<ReadonlyMap<string, ShareImage>> {
    const location = pathToFileURL(absolutePath("app.shareMap")).href + FRESH_QUERY + String(Date.now());
    const module: unknown = await import(location);
    if (!isShareMapModule(module)) {
        throw new TypeError(MALFORMED_SHARE_MAP + SUBJECT_SEPARATOR + location);
    }
    return module.SHARE_IMAGES;
};

const mapFindings = async function mapFindings(): Promise<readonly CardFinding[]> {
    const expected = shareEntriesOf(specs, RENDERS);
    const held = await shareMap();
    const mapCurrent =
        expected.length === held.size &&
        expected.every(({ page, ...share }) => JSON.stringify(held.get(page)) === JSON.stringify(share));
    return [
        ...(mapCurrent ? [] : [{ card: VALIDATION_COMMAND, message: STALE_MAP }]),
        ...unpublishedShares(expected, SHARES).map((file) => ({ card: file, message: MISSING_IMAGE })),
        ...strayShares(expected, SHARES).map((file) => ({ card: file, message: ORPHAN_IMAGE })),
    ];
};

const outputFindings = async function outputFindings(): Promise<readonly CardFinding[]> {
    const missing = missingImages(specs, RENDERS).map((file) => ({ card: file, message: MISSING_IMAGE }));
    return [
        ...missing,
        ...orphanImages(specs, RENDERS).map((file) => ({ card: file, message: ORPHAN_IMAGE })),
        ...staleEntries(specs, readLedger(absolutePath("app.shareLedger"))).map((entry) => ({
            card: entry,
            message: STALE_IMAGE,
        })),
        ...(missing.length === 0 ? await mapFindings() : []),
    ];
};

const heal = async function heal(): Promise<void> {
    say(HEALING);
    const capture = await openStage(true);
    try {
        const result = await exportCards({ force: false, gpu: false, only: null, url: capture.url }, specs);
        for (const key of result.rendered) {
            say(RENDERED_PREFIX + key);
        }
        for (const file of result.pruned) {
            say(PRUNED_PREFIX + file);
        }
    } finally {
        await capture.close();
    }
};

if (specFindings.length === 0 && (await outputFindings()).length > 0) {
    await heal();
}

conclude([...specFindings, ...(specFindings.length === 0 ? await outputFindings() : [])], CARDS_VALID);
```
