runtime/entrypoints/deployment.entrypoint.ts
runtime/entrypoints/deployment.entrypoint.ts is a file in Bane's Lab Deploy. 128 lines of code and 17 definitions.
import {
CONNECTED,
CONNECTING,
DEPLOY_DONE,
DEPLOY_FAILED,
DEPLOY_START,
FATAL,
ROLLBACK_FAILED,
SECONDS_SUFFIX,
SITEMAP_UNBUILT,
SUMMARY_BACKUP,
SUMMARY_DURATION,
SUMMARY_FILES,
SUMMARY_SIZE,
SUMMARY_TITLE,
UPLOAD_OF,
} from "#configuration/strings/deployment.strings";
import type { Journal, ManagedBackup, Outcome, Secrets, Shell, UploadStats } from "#types/deployment.types";
import { archiveSite, pruneArchives, restoreSite } from "#core/steps/backup.step";
import { buildSite, validateSite } from "#core/steps/site.step";
import { formatBytes, formatDuration } from "#core/converters/text.converter";
import { pullManaged, restoreManaged } from "#core/steps/recovery.step";
import { PAGE_SITEMAP_ROUTE } from "@banes-lab/build-scripts/configuration/constants/site.constants.ts";
import { SITE_URL } from "@banes-lab/web/core/assets/link.assets.ts";
import { createJournal } from "#core/reporters/journal.reporter";
import { createShell } from "#core/adapters/shell.adapter";
import { fetchIndex } from "#core/adapters/package.adapter";
import { loadSecrets } from "#core/loaders/environment.loader";
import { messageOf } from "#core/converters/failure.converter";
import { pingSearch } from "#core/steps/search.step";
import { prepareServer } from "#core/steps/setup.step";
import { probeCertificate } from "#core/steps/certificate.step";
import { probeSite } from "#core/steps/probe.step";
import { pruneRemoteRoot } from "#core/steps/remote.step";
import { readLive } from "#core/adapters/search.adapter";
import { readOrNull } from "@banes-lab/build-scripts/core/loaders/build.loader.ts";
import { reportDeployment } from "#core/reporters/deployment.reporter";
import { uploadSite } from "#core/steps/release.step";
import { verifyNginx } from "#core/steps/nginx.step";
const summarize = function summarize(journal: Journal, stats: UploadStats, outcome: Outcome): void {
journal.log(SUMMARY_TITLE);
journal.log(SUMMARY_FILES + String(stats.uploadedCount) + UPLOAD_OF + String(stats.totalFileCount));
journal.log(SUMMARY_SIZE + formatBytes(stats.totalBytes));
journal.log(SUMMARY_DURATION + formatDuration(outcome.span.finishedAt - outcome.span.startedAt) + SECONDS_SUFFIX);
journal.log(SUMMARY_BACKUP + outcome.backup);
journal.log(DEPLOY_DONE);
journal.log(SITE_URL);
};
const release = async function release(
shell: Shell,
journal: Journal,
secrets: Secrets,
startedAt: number,
): Promise<boolean> {
const backup = await archiveSite(shell, journal);
let managed: readonly ManagedBackup[] = [];
try {
pruneArchives(journal);
const stats = await uploadSite(shell, journal);
await pruneRemoteRoot(shell, journal);
managed = await pullManaged(shell, journal);
await prepareServer(shell, journal, fetchIndex);
await verifyNginx(shell, journal);
await probeSite(journal, SITE_URL, shell);
await probeCertificate(journal, SITE_URL);
const outcome: Outcome = { backup, failure: "", span: { finishedAt: Date.now(), startedAt }, stats };
const reported = await reportDeployment(journal, secrets.deployWebhook, outcome);
summarize(journal, stats, outcome);
return reported;
} catch (error: unknown) {
journal.error(DEPLOY_FAILED + messageOf(error));
try {
if (managed.length > 0) {
await restoreManaged(shell, journal, managed);
}
await restoreSite(shell, journal, backup);
} catch (rollbackError: unknown) {
journal.error(ROLLBACK_FAILED + messageOf(rollbackError));
}
throw error;
}
};
const ship = async function ship(
shell: Shell,
journal: Journal,
secrets: Secrets,
startedAt: number,
): Promise<boolean> {
journal.log(DEPLOY_START);
await buildSite(journal);
await validateSite(journal);
const before = await readLive(SITE_URL + PAGE_SITEMAP_ROUTE);
journal.log(CONNECTING);
await shell.connect();
journal.mark(CONNECTED);
const reported = await release(shell, journal, secrets, startedAt);
const after = readOrNull(PAGE_SITEMAP_ROUTE.slice(1));
if (after === null) {
throw new Error(SITEMAP_UNBUILT + PAGE_SITEMAP_ROUTE);
}
await pingSearch(journal, SITE_URL, secrets.searchKey, { after, before });
return reported;
};
const run = async function run(): Promise<number> {
const secrets = loadSecrets();
const journal = createJournal();
const shell = createShell(secrets);
const startedAt = Date.now();
try {
return (await ship(shell, journal, secrets, startedAt)) ? 0 : 1;
} catch (error: unknown) {
const failure = messageOf(error);
journal.error(DEPLOY_FAILED + failure);
const span = { finishedAt: Date.now(), startedAt };
await reportDeployment(journal, secrets.deployWebhook, { backup: "", failure, span, stats: null });
return 1;
} finally {
shell.dispose();
}
};
run()
.then((status) => {
process.exitCode = status;
})
.catch((error: unknown) => {
console.error(FATAL + messageOf(error));
process.exitCode = 1;
});