# core/steps/setup.step.ts

> 172 lines of code and 24 definitions.

Tree: Bane's Lab Deploy
Language: typescript
Layer: application
Canonical: https://banes-lab.com/anatomy/deploy#file-deploy-core-steps-setup-step-ts
Source text: https://banes-lab.com/source/deploy/core/steps/setup.step.ts.txt

Listed in [core/steps](https://banes-lab.com/api/source/deploy/core/steps.md), after [core/steps/search.step.ts](https://banes-lab.com/source/deploy/core/steps/search.step.ts.md) and before [core/steps/site.step.ts](https://banes-lab.com/source/deploy/core/steps/site.step.ts.md).

## Definitions

- `prepareServer` (lexical_declaration, line 175, exported)
- `changeServer` (lexical_declaration, line 124)
- `ensureServer` (lexical_declaration, line 153, exported)
- `required` (lexical_declaration, line 50)
- `revertStanzas` (lexical_declaration, line 79)
- `installedAll` (lexical_declaration, line 70)
- `contextOf` (lexical_declaration, line 140)
- `loadIndexes` (lexical_declaration, line 57)
- `indexes` (lexical_declaration, line 62)
- `version` (lexical_declaration, line 86)
- `planPackages` (lexical_declaration, line 96, exported)
- `stanzas` (lexical_declaration, line 101, exported)
- `target` (lexical_declaration, line 102, exported)
- `wanted` (lexical_declaration, line 106, exported)
- `installed` (lexical_declaration, line 112, exported)
- `install` (lexical_declaration, line 116, exported)
- `changing` (lexical_declaration, line 117, exported)
- `kept` (lexical_declaration, line 118, exported)
- `revert` (lexical_declaration, line 119, exported)
- `added` (lexical_declaration, line 120, exported)
- `platform` (lexical_declaration, line 145)
- `plan` (lexical_declaration, line 149)
- `context` (lexical_declaration, line 159, exported)
- `current` (lexical_declaration, line 160, exported)

## Contained in

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

## Uses

- [core/converters/failure.converter.ts](https://banes-lab.com/source/deploy/core/converters/failure.converter.ts.md)
- [core/converters/index.converter.ts](https://banes-lab.com/source/deploy/core/converters/index.converter.ts.md)
- [core/pipelines/setup.pipeline.ts](https://banes-lab.com/source/deploy/core/pipelines/setup.pipeline.ts.md)
- [core/steps/build.step.ts](https://banes-lab.com/source/deploy/core/steps/build.step.ts.md)

## Used by

- [runtime/entrypoints/deployment.entrypoint.ts](https://banes-lab.com/source/deploy/runtime/entrypoints/deployment.entrypoint.ts.md)
- [runtime/entrypoints/nginx.entrypoint.ts](https://banes-lab.com/source/deploy/runtime/entrypoints/nginx.entrypoint.ts.md)

## Linked from

- [core/converters](https://banes-lab.com/anatomy/deploy/folder-deploy-core-converters.md)
- [core/pipelines](https://banes-lab.com/anatomy/deploy/folder-deploy-core-pipelines.md)
- [core/steps](https://banes-lab.com/anatomy/deploy/folder-deploy-core-steps.md)
- [runtime/entrypoints](https://banes-lab.com/anatomy/deploy/folder-deploy-runtime-entrypoints.md)

## Source

```typescript
import {
    APPLY_EFFECTS,
    PUBLISH_EFFECTS,
    REVERT_EFFECTS,
    STAGE_EFFECTS,
    UPKEEP_EFFECTS,
    runEffects,
} from "#core/pipelines/setup.pipeline";
import {
    ARCHITECTURE_COMMAND,
    CODENAME_COMMAND,
    MODULE_PACKAGES,
    NGINX_PACKAGE,
    NGINX_SOURCE,
    PACKAGE_SOURCES,
    PACKAGE_STATUS_COMMAND,
    SETUP_STAGING,
} from "#configuration/constants/nginx.constants";
import type {
    IndexFetcher,
    InstalledPackage,
    Journal,
    PackagePlan,
    PackageStanza,
    Platform,
    SetupContext,
    Shell,
} from "#types/deployment.types";
import {
    PACKAGE_REVERT_FAILED,
    PACKAGE_REVERT_UNAVAILABLE,
    PACKAGE_UNAVAILABLE,
    SERVER_CHECKING,
    SERVER_CURRENT,
    SERVER_READY,
} from "#configuration/strings/nginx.strings";
import {
    codenameOf,
    companionOf,
    indexUrl,
    installedOf,
    isCurrent,
    serverTarget,
    stanzaAt,
    stanzasOf,
} from "#core/converters/index.converter";
import { messageOf } from "#core/converters/failure.converter";
import { staleModules } from "#core/steps/build.step";

const required = function required<T>(value: T | null, message: string): T {
    if (value === null) {
        throw new Error(message);
    }
    return value;
};

const loadIndexes = async function loadIndexes(
    fetchIndex: IndexFetcher,
    codename: string,
    architecture: string,
): Promise<readonly PackageStanza[]> {
    const indexes = await Promise.all(
        PACKAGE_SOURCES.map(async (source) =>
            stanzasOf(await fetchIndex(indexUrl(source, codename, architecture)), source),
        ),
    );
    return indexes.flat();
};

const installedAll = async function installedAll(
    shell: Shell,
    names: readonly string[],
): Promise<readonly InstalledPackage[]> {
    return Promise.all(
        names.map(async (name) => installedOf(name, (await shell.run(`${PACKAGE_STATUS_COMMAND} ${name}`)).stdout)),
    );
};

const revertStanzas = async function revertStanzas(
    fetchIndex: IndexFetcher,
    kept: readonly InstalledPackage[],
    architecture: string,
): Promise<readonly PackageStanza[]> {
    return Promise.all(
        kept.map(async (installed) => {
            const version = installed.version ?? "";
            const stanzas = await loadIndexes(fetchIndex, codenameOf(version), architecture);
            return required(
                stanzaAt(stanzas, installed.name, version),
                `${PACKAGE_REVERT_UNAVAILABLE}${installed.name} ${version}`,
            );
        }),
    );
};

export const planPackages = async function planPackages(
    shell: Shell,
    fetchIndex: IndexFetcher,
    platform: Platform,
): Promise<PackagePlan> {
    const stanzas = await loadIndexes(fetchIndex, platform.codename, platform.architecture);
    const target = required(
        serverTarget(stanzas, NGINX_SOURCE.upstream),
        `${PACKAGE_UNAVAILABLE}${NGINX_PACKAGE} ${NGINX_SOURCE.upstream} ${platform.codename}`,
    );
    const wanted = [
        target,
        ...MODULE_PACKAGES.map((name) =>
            required(companionOf(stanzas, name, target), `${PACKAGE_UNAVAILABLE}${name} ${target.version}`),
        ),
    ];
    const installed = await installedAll(
        shell,
        wanted.map((stanza) => stanza.name),
    );
    const install = wanted.filter((stanza, at) => installed[at] === undefined || !isCurrent(installed[at], stanza));
    const changing = installed.filter((entry) => install.some((stanza) => stanza.name === entry.name));
    const kept = changing.filter((entry) => entry.configured && entry.version !== null);
    const revert = await revertStanzas(fetchIndex, kept, platform.architecture);
    const added = changing.filter((entry) => !entry.configured).map((entry) => entry.name);
    return { added, install, revert };
};

const changeServer = async function changeServer(context: SetupContext): Promise<void> {
    await runEffects(STAGE_EFFECTS, context);
    try {
        await runEffects(APPLY_EFFECTS, context);
    } catch (error: unknown) {
        try {
            await runEffects(REVERT_EFFECTS, context);
        } catch (revertError: unknown) {
            throw new Error(`${messageOf(error)} ${PACKAGE_REVERT_FAILED}${messageOf(revertError)}`, {
                cause: revertError,
            });
        }
        throw error;
    }
};

const contextOf = async function contextOf(
    shell: Shell,
    journal: Journal,
    fetchIndex: IndexFetcher,
): Promise<SetupContext> {
    const platform: Platform = {
        architecture: (await shell.run(ARCHITECTURE_COMMAND)).stdout.trim(),
        codename: (await shell.run(CODENAME_COMMAND)).stdout.trim(),
    };
    const plan = await planPackages(shell, fetchIndex, platform);
    return { fetchIndex, journal, modules: await staleModules(shell), plan, shell };
};

export const ensureServer = async function ensureServer(
    shell: Shell,
    journal: Journal,
    fetchIndex: IndexFetcher,
): Promise<SetupContext> {
    journal.log(SERVER_CHECKING);
    const context = await contextOf(shell, journal, fetchIndex);
    const current = context.plan.install.length === 0 && context.modules.length === 0;
    try {
        if (current) {
            journal.log(SERVER_CURRENT);
            await runEffects(UPKEEP_EFFECTS, context);
        } else {
            await changeServer(context);
        }
    } finally {
        await shell.run(`rm -rf ${SETUP_STAGING}`);
    }
    journal.log(SERVER_READY);
    return context;
};

export const prepareServer = async function prepareServer(
    shell: Shell,
    journal: Journal,
    fetchIndex: IndexFetcher,
): Promise<void> {
    await runEffects(PUBLISH_EFFECTS, await ensureServer(shell, journal, fetchIndex));
};
```
