# core/converters/build.converter.ts

> 78 lines of code and 27 definitions.

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

Listed in [core/converters](https://banes-lab.com/api/source/build-scripts/core/converters.md), after [core/converters/browser.converter.ts](https://banes-lab.com/source/build-scripts/core/converters/browser.converter.ts.md) and before [core/converters/catalog.converter.ts](https://banes-lab.com/source/build-scripts/core/converters/catalog.converter.ts.md).

## Definitions

- `pngPixelsOf` (lexical_declaration, line 60, exported)
- `readArtifact` (lexical_declaration, line 36, exported)
- `tabIdsOf` (lexical_declaration, line 67, exported)
- `MAIN_SELECTOR` (lexical_declaration, line 9)
- `CANONICAL_SELECTOR` (lexical_declaration, line 10)
- `MARKDOWN_ALTERNATE_SELECTOR` (lexical_declaration, line 11)
- `JSON_ALTERNATE_SELECTOR` (lexical_declaration, line 12)
- `DESCRIPTION_SELECTOR` (lexical_declaration, line 13)
- `ROBOTS_SELECTOR` (lexical_declaration, line 14)
- `SCHEMA_SELECTOR` (lexical_declaration, line 15)
- `LINK_SELECTOR` (lexical_declaration, line 16)
- `LOC_OPEN` (lexical_declaration, line 17)
- `LOC_CLOSE` (lexical_declaration, line 18)
- `artifactOf` (lexical_declaration, line 20)
- `locationsOf` (lexical_declaration, line 40, exported)
- `found` (lexical_declaration, line 41, exported)
- `cursor` (lexical_declaration, line 42, exported)
- `start` (lexical_declaration, line 44, exported)
- `end` (lexical_declaration, line 45, exported)
- `PNG_SIGNATURE` (lexical_declaration, line 55)
- `PNG_WIDTH_OFFSET` (lexical_declaration, line 56)
- `PNG_HEIGHT_OFFSET` (lexical_declaration, line 57)
- `PNG_HEADER_BYTES` (lexical_declaration, line 58)
- `id` (lexical_declaration, line 69, exported)
- `buildRoutesOf` (lexical_declaration, line 74, exported)
- `payload` (lexical_declaration, line 79, exported)
- `tabs` (lexical_declaration, line 80, exported)

## Contained in

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

## Uses

- [core/adapters/document.adapter.ts](https://banes-lab.com/source/build-scripts/core/adapters/document.adapter.ts.md)
- [core/converters/site.converter.ts](https://banes-lab.com/source/build-scripts/core/converters/site.converter.ts.md)

## Used by

- [core/coordinators/validation.coordinator.ts](https://banes-lab.com/source/build-scripts/core/coordinators/validation.coordinator.ts.md)

## Linked from

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

## Source

```typescript
import { CONTENT_ATTRIBUTE, HREF_ATTRIBUTE } from "#core/ids/element.ids";
import type { PageArtifact, ValidationRoute } from "#types/validation.types";
import { contentOf, tabsIn, textAt } from "@banes-lab/content/core/selectors/payload.selector.ts";
import { pagePath, tabLink } from "@banes-lab/web/core/assets/link.assets.ts";
import { Buffer } from "node:buffer";
import { PAYLOAD_ID_KEY } from "@banes-lab/content/configuration/constants/leak.constants.ts";
import { readDocument } from "#core/adapters/document.adapter";

const MAIN_SELECTOR = "main";
const CANONICAL_SELECTOR = 'link[rel="canonical"]';
const MARKDOWN_ALTERNATE_SELECTOR = 'link[rel="alternate"][type="text/markdown"]';
const JSON_ALTERNATE_SELECTOR = 'link[rel="alternate"][type="application/json"]';
const DESCRIPTION_SELECTOR = 'meta[name="description"]';
const ROBOTS_SELECTOR = 'meta[name="robots"]';
const SCHEMA_SELECTOR = 'script[type="application/ld+json"]';
const LINK_SELECTOR = "a[href]";
const LOC_OPEN = "<loc>";
const LOC_CLOSE = "</loc>";

const artifactOf = function artifactOf(document: Document): PageArtifact {
    return {
        alternates: {
            json: document.querySelector(JSON_ALTERNATE_SELECTOR)?.getAttribute(HREF_ATTRIBUTE) ?? null,
            markdown: document.querySelector(MARKDOWN_ALTERNATE_SELECTOR)?.getAttribute(HREF_ATTRIBUTE) ?? null,
        },
        canonical: document.querySelector(CANONICAL_SELECTOR)?.getAttribute(HREF_ATTRIBUTE) ?? null,
        description: document.querySelector(DESCRIPTION_SELECTOR)?.getAttribute(CONTENT_ATTRIBUTE) ?? null,
        links: [...document.querySelectorAll(LINK_SELECTOR)].map((anchor) => anchor.getAttribute(HREF_ATTRIBUTE) ?? ""),
        robots: document.querySelector(ROBOTS_SELECTOR)?.getAttribute(CONTENT_ATTRIBUTE) ?? null,
        schema: document.querySelector(SCHEMA_SELECTOR)?.textContent ?? null,
        text: document.querySelector(MAIN_SELECTOR)?.textContent.trim() ?? "",
        title: document.title,
    };
};

export const readArtifact = function readArtifact(html: string): PageArtifact {
    return readDocument(html, artifactOf);
};

export const locationsOf = function locationsOf(xml: string): string[] {
    const found: string[] = [];
    let cursor = xml.indexOf(LOC_OPEN);
    while (cursor !== -1) {
        const start = cursor + LOC_OPEN.length;
        const end = xml.indexOf(LOC_CLOSE, start);
        if (end === -1) {
            break;
        }
        found.push(xml.slice(start, end));
        cursor = xml.indexOf(LOC_OPEN, end);
    }
    return found;
};

const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
const PNG_WIDTH_OFFSET = 16;
const PNG_HEIGHT_OFFSET = 20;
const PNG_HEADER_BYTES = 24;

export const pngPixelsOf = function pngPixelsOf(bytes: Buffer): number | null {
    if (bytes.length < PNG_HEADER_BYTES || !bytes.subarray(0, PNG_SIGNATURE.length).equals(PNG_SIGNATURE)) {
        return null;
    }
    return bytes.readUInt32BE(PNG_WIDTH_OFFSET) * bytes.readUInt32BE(PNG_HEIGHT_OFFSET);
};

export const tabIdsOf = function tabIdsOf(raw: string): string[] {
    return tabsIn(contentOf(JSON.parse(raw))).flatMap((tab) => {
        const id = textAt(tab, PAYLOAD_ID_KEY);
        return id === null ? [] : [id];
    });
};

export const buildRoutesOf = function buildRoutesOf(
    ids: readonly string[],
    payloadOf: (page: string) => string | null,
): ValidationRoute[] {
    return ids.flatMap((page) => {
        const payload = payloadOf(page);
        const tabs = payload === null ? [] : tabIdsOf(payload).slice(1);
        return [
            { page, path: pagePath(page), tab: null },
            ...tabs.map((tab) => ({ page, path: tabLink(page, tab), tab })),
        ];
    });
};
```
