# core/validators/manifest.validator.ts

> 101 lines of code and 18 definitions.

Tree: GovLab Docs
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/docs#file-docs-core-validators-manifest-validator-ts
Source text: https://banes-lab.com/source/docs/core/validators/manifest.validator.ts.txt

Listed in [core/validators](https://banes-lab.com/api/source/docs/core/validators.md), after [core/validators/manifest.prose.validator.ts](https://banes-lab.com/source/docs/core/validators/manifest.prose.validator.ts.md) and before [core/validators/metadata.validator.ts](https://banes-lab.com/source/docs/core/validators/metadata.validator.ts.md).

## Definitions

- `coreFieldErrors` (lexical_declaration, line 79)
- `validateManifest` (lexical_declaration, line 99, exported)
- `isRelationshipArray` (lexical_declaration, line 25)
- `keyErrors` (lexical_declaration, line 89)
- `visibilityErrors` (lexical_declaration, line 38)
- `ecosystemErrors` (lexical_declaration, line 68)
- `relationshipErrors` (lexical_declaration, line 73)
- `capabilitiesErrors` (lexical_declaration, line 55)
- `maturityErrors` (lexical_declaration, line 63)
- `REQUIRED_TEXT_FIELDS` (lexical_declaration, line 22)
- `OPTIONAL_FLAG_TYPES` (lexical_declaration, line 23)
- `allowedKeys` (lexical_declaration, line 34)
- `{ visibility }` (lexical_declaration, line 39)
- `flagErrors` (lexical_declaration, line 46)
- `{ capabilities }` (lexical_declaration, line 56)
- `{ maturity }` (lexical_declaration, line 64)
- `{ ecosystem }` (lexical_declaration, line 69)
- `allowed` (lexical_declaration, line 90)

## Contained in

- [core/validators](https://banes-lab.com/anatomy/docs/folder-docs-core-validators.md)

## Uses

- [configuration/strings/manifest.strings.ts](https://banes-lab.com/source/docs/configuration/strings/manifest.strings.ts.md)
- [core/predicates/readme.predicate.ts](https://banes-lab.com/source/docs/core/predicates/readme.predicate.ts.md)
- [core/predicates/record.predicate.ts](https://banes-lab.com/source/docs/core/predicates/record.predicate.ts.md)

## Used by

- [core/registries/manifest.registry.ts](https://banes-lab.com/source/docs/core/registries/manifest.registry.ts.md)

## Linked from

- [configuration/strings](https://banes-lab.com/anatomy/docs/folder-docs-configuration-strings.md)
- [core/predicates](https://banes-lab.com/anatomy/docs/folder-docs-core-predicates.md)
- [core/registries](https://banes-lab.com/anatomy/docs/folder-docs-core-registries.md)
- [core/validators](https://banes-lab.com/anatomy/docs/folder-docs-core-validators.md)

## Source

```typescript
import {
    COMPUTED_KEYS,
    CORE_KEYS,
    MATURITY,
    RELATIONSHIP_KEYS,
    VISIBILITY_FLAGS,
} from "#configuration/constants/manifest.constants";
import {
    MANIFEST_ERRORS,
    computedField,
    nonEmptyField,
    relationshipShape,
    unknownKey,
    unknownVisibilityKey,
    visibilityFlag,
} from "#configuration/strings/manifest.strings";
import type { Manifest } from "#types/readme.types";
import type { ManifestPlugin } from "#types/manifest.types";
import { isNonEmptyString } from "#core/predicates/readme.predicate";
import { isPlainRecord } from "#core/predicates/record.predicate";

const REQUIRED_TEXT_FIELDS: readonly string[] = ["label", "summary"];
const OPTIONAL_FLAG_TYPES: ReadonlySet<string> = new Set(["boolean", "undefined"]);

const isRelationshipArray = function isRelationshipArray(value: unknown): boolean {
    return (
        Array.isArray(value) &&
        value.every(
            (entry) => isPlainRecord(entry) && isNonEmptyString(entry["package"]) && isNonEmptyString(entry["reason"]),
        )
    );
};

const allowedKeys = function allowedKeys(plugins: readonly ManifestPlugin[]): Set<string> {
    return new Set([...CORE_KEYS, ...plugins.flatMap((plugin) => plugin.section?.keys ?? [])]);
};

const visibilityErrors = function visibilityErrors(manifest: Manifest): string[] {
    const { visibility } = manifest;
    if (visibility === undefined) {
        return [MANIFEST_ERRORS.visibilityRequired];
    }
    if (!isPlainRecord(visibility)) {
        return [MANIFEST_ERRORS.visibilityShape];
    }
    const flagErrors = VISIBILITY_FLAGS.filter((flag) => !OPTIONAL_FLAG_TYPES.has(typeof visibility[flag])).map(
        visibilityFlag,
    );
    const keyErrors = Object.keys(visibility)
        .filter((key) => !VISIBILITY_FLAGS.includes(key))
        .map(unknownVisibilityKey);
    return [...flagErrors, ...keyErrors];
};

const capabilitiesErrors = function capabilitiesErrors(manifest: Manifest): string[] {
    const { capabilities } = manifest;
    if (capabilities === undefined) {
        return [];
    }
    return Array.isArray(capabilities) && capabilities.every(isNonEmptyString) ? [] : [MANIFEST_ERRORS.capabilities];
};

const maturityErrors = function maturityErrors(manifest: Manifest): string[] {
    const { maturity } = manifest;
    return typeof maturity === "string" && MATURITY.has(maturity) ? [] : [MANIFEST_ERRORS.maturity];
};

const ecosystemErrors = function ecosystemErrors(manifest: Manifest): string[] {
    const { ecosystem } = manifest;
    return ecosystem !== undefined && !isNonEmptyString(ecosystem) ? [MANIFEST_ERRORS.ecosystem] : [];
};

const relationshipErrors = function relationshipErrors(manifest: Manifest): string[] {
    return RELATIONSHIP_KEYS.filter(
        (field) => manifest[field] !== undefined && !isRelationshipArray(manifest[field]),
    ).map(relationshipShape);
};

const coreFieldErrors = function coreFieldErrors(manifest: Manifest): string[] {
    return [
        ...REQUIRED_TEXT_FIELDS.filter((field) => !isNonEmptyString(manifest[field])).map(nonEmptyField),
        ...maturityErrors(manifest),
        ...capabilitiesErrors(manifest),
        ...ecosystemErrors(manifest),
        ...relationshipErrors(manifest),
    ];
};

const keyErrors = function keyErrors(manifest: Manifest, plugins: readonly ManifestPlugin[]): string[] {
    const allowed = allowedKeys(plugins);
    return Object.keys(manifest).flatMap((key) => {
        if (COMPUTED_KEYS.has(key)) {
            return [computedField(key)];
        }
        return allowed.has(key) ? [] : [unknownKey(key)];
    });
};

export const validateManifest = function validateManifest(
    manifest: unknown,
    plugins: readonly ManifestPlugin[] = [],
): string[] {
    if (!isPlainRecord(manifest)) {
        return [MANIFEST_ERRORS.missingManifest];
    }
    return [
        ...coreFieldErrors(manifest),
        ...visibilityErrors(manifest),
        ...keyErrors(manifest, plugins),
        ...plugins.flatMap((plugin) => plugin.section?.validate?.(manifest) ?? []),
    ];
};
```
