397 lines
21 KiB
TypeScript
397 lines
21 KiB
TypeScript
import type { RenderedCliResult } from "./output";
|
|
|
|
export function renderMirrorPlan(result: Record<string, unknown>): RenderedCliResult {
|
|
const target = record(result.target);
|
|
const source = record(result.sourceAuthority);
|
|
const repos = arrayRecords(result.repositories).map((repo) => [stringValue(repo.key), stringValue(repo.upstreamRepository), stringValue(repo.upstreamBranch), stringValue(repo.readUrl), stringValue(repo.legacyDisposition)]);
|
|
const responsibilities = arrayRecords(result.responsibilities).map((item) => [stringValue(item.name), stringValue(item.current), stringValue(item.target), stringValue(item.disposition)]);
|
|
const next = record(result.next);
|
|
return rendered(result, "platform-infra gitea mirror plan", [
|
|
"PLATFORM-INFRA GITEA MIRROR PLAN",
|
|
...table(["TARGET", "STAGE", "AUTHORITY", "REPOS"], [[stringValue(target.id), stringValue(source.stage), stringValue(source.statusAuthority), stringValue(source.repositoryCount)]]),
|
|
"",
|
|
"REPOSITORIES",
|
|
...(repos.length === 0 ? ["-"] : table(["KEY", "UPSTREAM", "BRANCH", "GITEA_READ_URL", "LEGACY"], repos)),
|
|
"",
|
|
"RESPONSIBILITIES",
|
|
...(responsibilities.length === 0 ? ["-"] : table(["NAME", "CURRENT", "TARGET", "DISPOSITION"], responsibilities)),
|
|
"",
|
|
"NEXT",
|
|
` status: ${stringValue(next.status)}`,
|
|
` full: ${stringValue(next.statusFull)}`,
|
|
` webhook-status: ${stringValue(next.webhookStatus)}`,
|
|
` fix-automatic-delivery: ${stringValue(next.fixAutomaticDelivery)}`,
|
|
"",
|
|
"Disclosure: credentials are summarized by presence/fingerprint only.",
|
|
]);
|
|
}
|
|
|
|
export function renderMirrorBootstrap(result: Record<string, unknown>): RenderedCliResult {
|
|
const repos = arrayRecords(result.repositories).map((repo) => [
|
|
stringValue(repo.key),
|
|
stringValue(repo.owner, stringValue(repo.giteaOwner)),
|
|
stringValue(repo.name, stringValue(repo.giteaRepo)),
|
|
result.mode === "dry-run" ? "planned" : boolText(record(repo.create).ok),
|
|
]);
|
|
const blockers = Array.isArray(result.blockers) ? result.blockers.map(String) : [];
|
|
const next = record(result.next);
|
|
return rendered(result, "platform-infra gitea mirror bootstrap", [
|
|
"PLATFORM-INFRA GITEA MIRROR BOOTSTRAP",
|
|
...table(["OK", "MODE", "MUTATION", "TARGET"], [[boolText(result.ok), stringValue(result.mode), boolText(result.mutation), stringValue(record(result.target).id)]]),
|
|
"",
|
|
"REPOSITORIES",
|
|
...(repos.length === 0 ? ["-"] : table(["KEY", "OWNER", "REPO", "STATE"], repos)),
|
|
...(blockers.length === 0 ? [] : ["", `BLOCKERS ${blockers.join(",")}`]),
|
|
...remoteErrorLines(result),
|
|
"",
|
|
`NEXT ${stringValue(result.mode === "dry-run" ? next.confirm : next.webhookStatus)}`,
|
|
]);
|
|
}
|
|
|
|
export function renderMirrorSync(result: Record<string, unknown>): RenderedCliResult {
|
|
const bundles = arrayRecords(result.sourceBundles).map((bundle) => [stringValue(bundle.key), stringValue(bundle.sourceCommit).slice(0, 12), stringValue(bundle.snapshotRef), stringValue(bundle.readUrl)]);
|
|
const repos = arrayRecords(result.repositories).map((repo) => [
|
|
stringValue(repo.key),
|
|
boolText(repo.syncOk),
|
|
stringValue(repo.fetchRc),
|
|
stringValue(repo.pushRc),
|
|
stringValue(repo.gitopsPushRc),
|
|
stringValue(repo.sourceCommit).slice(0, 12),
|
|
compactTail(stringValue(repo.fetchTail) || stringValue(repo.pushTail) || stringValue(repo.revparseTail)),
|
|
]);
|
|
const next = record(result.next);
|
|
return rendered(result, "platform-infra gitea mirror sync", [
|
|
"PLATFORM-INFRA GITEA MIRROR SYNC",
|
|
...table(["OK", "MUTATION", "TARGET"], [[boolText(result.ok), boolText(result.mutation), stringValue(record(result.target).id)]]),
|
|
"",
|
|
"REPOSITORIES",
|
|
...(repos.length === 0 ? ["-"] : table(["KEY", "SYNC", "FETCH", "PUSH", "GITOPS", "COMMIT", "ERROR"], repos)),
|
|
"",
|
|
"SOURCE BUNDLES",
|
|
...(bundles.length === 0 ? ["-"] : table(["KEY", "COMMIT", "SNAPSHOT_REF", "READ_URL"], bundles)),
|
|
...remoteErrorLines(result),
|
|
"",
|
|
`NEXT ${stringValue(next.status)}`,
|
|
]);
|
|
}
|
|
|
|
export function renderMirrorStatus(result: Record<string, unknown>): RenderedCliResult {
|
|
const source = record(result.sourceAuthority);
|
|
const service = record(result.serviceHealth);
|
|
const repos = arrayRecords(result.repositories).map((repo) => [stringValue(repo.key), stringValue(repo.mirrorState), stringValue(repo.branchCommit).slice(0, 12), stringValue(repo.snapshotCommit).slice(0, 12), stringValue(repo.snapshotRef)]);
|
|
const next = record(result.next);
|
|
return rendered(result, "platform-infra gitea mirror status", [
|
|
"PLATFORM-INFRA GITEA MIRROR STATUS",
|
|
...table(["TARGET", "SERVICE", "MIRROR_READY", "AUTHORITY"], [[stringValue(record(result.target).id), boolText(service.ok), boolText(source.mirrorReady), stringValue(source.statusAuthority)]]),
|
|
"",
|
|
"REPOSITORIES",
|
|
...(repos.length === 0 ? ["-"] : table(["KEY", "STATE", "BRANCH", "SNAPSHOT", "SNAPSHOT_REF"], repos)),
|
|
...remoteErrorLines(result),
|
|
"",
|
|
`NEXT ${stringValue(next.webhookStatus)}`,
|
|
]);
|
|
}
|
|
|
|
export function renderMirrorWebhookApply(result: Record<string, unknown>): RenderedCliResult {
|
|
const repos = arrayRecords(result.repositories).map((repo) => [stringValue(repo.key), stringValue(repo.repository), boolText(repo.hookReady), stringValue(repo.hookId), compactTail(stringValue(repo.error))]);
|
|
const next = record(result.next);
|
|
return rendered(result, "platform-infra gitea mirror webhook apply", [
|
|
"PLATFORM-INFRA GITEA MIRROR WEBHOOK APPLY",
|
|
...table(["OK", "MUTATION", "TARGET", "URL"], [[boolText(result.ok), boolText(result.mutation), stringValue(record(result.target).id), stringValue(record(result.webhook).publicUrl)]]),
|
|
"",
|
|
"GITHUB HOOKS",
|
|
...(repos.length === 0 ? ["-"] : table(["KEY", "REPOSITORY", "READY", "HOOK_ID", "ERROR"], repos)),
|
|
...remoteErrorLines(result),
|
|
"",
|
|
`NEXT ${stringValue(next.webhookStatus)}`,
|
|
]);
|
|
}
|
|
|
|
export function renderMirrorWebhookCredentialBlocked(result: Record<string, unknown>): RenderedCliResult {
|
|
const target = record(result.target);
|
|
const error = record(result.error);
|
|
const blockerIds = Array.isArray(error.blockers) ? error.blockers.map(String) : [];
|
|
const repositories = arrayRecords(result.repositories);
|
|
const credentials = arrayRecords(result.credentials);
|
|
const rows = blockerIds.map((blocker) => {
|
|
const credential = credentials.find((item) => stringValue(item.id) === blocker) ?? {};
|
|
const permissionResult = record(credential.permissionResult);
|
|
const permissions = record(permissionResult.permissions);
|
|
const repoKey = blocker.includes(":") ? blocker.slice(blocker.indexOf(":") + 1) : "";
|
|
const repository = repositories.find((item) => stringValue(item.key) === repoKey) ?? {};
|
|
const permissionText = [
|
|
`status=${stringValue(permissionResult.status)}`,
|
|
`contents=${stringValue(permissions.contents)}`,
|
|
`metadata=${stringValue(permissions.metadata)}`,
|
|
`webhooks=${stringValue(permissions.webhooks)}`,
|
|
].join(",");
|
|
return [
|
|
stringValue(target.id),
|
|
stringValue(credential.repository, stringValue(repository.upstreamRepository)),
|
|
stringValue(credential.sourceRef),
|
|
boolText(credential.present),
|
|
stringValue(credential.fingerprint),
|
|
permissionText,
|
|
blocker,
|
|
boolText(credential.valuesPrinted),
|
|
];
|
|
});
|
|
const next = record(result.next);
|
|
return rendered(result, "platform-infra gitea mirror webhook status", [
|
|
"PLATFORM-INFRA GITEA MIRROR WEBHOOK STATUS",
|
|
...(rows.length === 0 ? ["-"] : table(["TARGET", "REPOSITORY", "SOURCE_REF", "PRESENT", "FINGERPRINT", "PERMISSION_RESULT", "BLOCKER", "VALUES_PRINTED"], rows)),
|
|
"",
|
|
`NEXT ${stringValue(next.webhookStatusFull)}`,
|
|
]);
|
|
}
|
|
|
|
export function renderMirrorWebhookStatus(result: Record<string, unknown>, disclosure: Record<string, unknown>): RenderedCliResult {
|
|
const repos = arrayRecords(result.repositories).map((repo) => {
|
|
const delivery = record(repo.selectedPushDelivery);
|
|
const topology = record(repo.hookTopology);
|
|
const topologyDrift = Array.isArray(topology.driftTypes) && topology.driftTypes.length > 0
|
|
? topology.driftTypes.join(",")
|
|
: topology.ready === true ? "ready" : "unknown";
|
|
const inbox = record(repo.durableInboxRecord);
|
|
const inboxResult = record(inbox.result);
|
|
const inboxState = stringValue(repo.durableInboxState, "missing");
|
|
const durableProof = repo.preDurableBootstrap === true
|
|
? "pre-durable-bootstrap"
|
|
: repo.durableInboxCommitted === true
|
|
? `${stringValue(inboxResult.disposition)}:${stringValue(inboxResult.sourceCommit).slice(0, 12)}`
|
|
: "-";
|
|
return [
|
|
stringValue(repo.key),
|
|
boolText(repo.hookReady),
|
|
stringValue(repo.githubHead).slice(0, 12),
|
|
stringValue(repo.branchCommit).slice(0, 12),
|
|
stringValue(repo.snapshotCommit).slice(0, 12),
|
|
boolText(repo.authorityMatches),
|
|
boolText(repo.bridgeEventStale),
|
|
topologyDrift,
|
|
`${stringValue(delivery.deliveryId)}:${stringValue(delivery.statusCode)}:${stringValue(delivery.status)}`,
|
|
inboxState,
|
|
durableProof,
|
|
compactTail(stringValue(repo.error)),
|
|
];
|
|
});
|
|
const bridge = record(result.bridge);
|
|
const bridgeLogs = record(result.bridgeLogs);
|
|
const recovery = record(record(bridge.durableInbox).failedDeliveryRecovery);
|
|
const recoveryCounts = record(recovery.counts);
|
|
const recoveryText = `${stringValue(recovery.state)}:${stringValue(recoveryCounts.pending, "0")}/${stringValue(recoveryCounts.attempted, "0")}/${stringValue(recoveryCounts["delivery-succeeded-awaiting-source"], "0")}/${stringValue(recoveryCounts["source-committed"], "0")}/${stringValue(recoveryCounts["terminal-exhausted"], "0")}`;
|
|
const webhook = record(result.webhook);
|
|
const retry = record(record(webhook.bridge).retry);
|
|
const hookReconcile = record(webhook.hookReconcile);
|
|
const retryText = `${stringValue(retry.maxAttempts)}x/${stringValue(retry.initialDelayMs)}-${stringValue(retry.maxDelayMs)}ms`;
|
|
const selection = record(disclosure.selection);
|
|
const selectedRepoKey = stringValue(selection.selectedRepoKey, "");
|
|
const delivery = arrayRecords(disclosure.deliveries)[0];
|
|
const deliveryResult = record(delivery?.result);
|
|
const retryDetail = record(disclosure.retry);
|
|
const logs = record(disclosure.logs);
|
|
const logRows = arrayRecords(logs.events).map((event) => [
|
|
stringValue(event.syncAttempt),
|
|
boolText(event.ok),
|
|
boolText(event.terminal),
|
|
stringValue(event.disposition),
|
|
stringValue(event.elapsedMs),
|
|
compactLine(stringValue(event.error)),
|
|
]);
|
|
const firstError = record(record(disclosure.diagnostics).firstError);
|
|
const repositoryCommands = arrayRecords(record(disclosure.next).repositories).map((item) => [
|
|
stringValue(item.key),
|
|
stringValue(item.deliveryId),
|
|
stringValue(item.command),
|
|
]);
|
|
const drillDown = selectedRepoKey === ""
|
|
? [
|
|
"",
|
|
"DRILL-DOWN",
|
|
...(repositoryCommands.length === 0 ? ["-"] : table(["REPOSITORY", "DELIVERY", "COMMAND"], repositoryCommands)),
|
|
]
|
|
: [
|
|
"",
|
|
"DELIVERY",
|
|
...(delivery === undefined ? ["-"] : table(["ID", "REPOSITORY", "STATE", "ATTEMPTS", "NEXT", "COMMIT", "DISPOSITION"], [[
|
|
stringValue(delivery.deliveryId),
|
|
stringValue(delivery.repo),
|
|
stringValue(delivery.state),
|
|
`${stringValue(retryDetail.cycleAttempt)}/${stringValue(retryDetail.totalAttempts)}`,
|
|
stringValue(retryDetail.nextAttemptAt),
|
|
stringValue(deliveryResult.sourceCommit).slice(0, 12),
|
|
stringValue(deliveryResult.disposition),
|
|
]])),
|
|
"",
|
|
"BRIDGE EVENTS",
|
|
...(logRows.length === 0 ? ["-"] : table(["ATTEMPT", "OK", "TERMINAL", "DISPOSITION", "ELAPSED_MS", "ERROR"], logRows)),
|
|
...(firstError.message === undefined ? [] : ["", `FIRST ERROR ${stringValue(firstError.domain)}: ${stringValue(firstError.message)}`]),
|
|
];
|
|
return rendered(disclosure, "platform-infra gitea mirror webhook status", [
|
|
"PLATFORM-INFRA GITEA MIRROR WEBHOOK STATUS",
|
|
...table(["TARGET", "BRIDGE", "READY", "RETRY", "RECOVERY", "URL_SHA", "LOGS"], [[stringValue(record(result.target).id), stringValue(bridge.deployment), boolText(bridge.ready), retryText, recoveryText, stringValue(hookReconcile.desiredUrlSha256), `rc=${stringValue(bridgeLogs.exitCode)}`]]),
|
|
"",
|
|
"GITHUB HOOKS",
|
|
...(repos.length === 0 ? ["-"] : table(["KEY", "HOOK", "GH_HEAD", "GITEA", "SNAPSHOT", "MATCH", "STALE", "TOPOLOGY", "DELIVERY", "DURABLE", "PROOF", "ERROR"], repos)),
|
|
...drillDown,
|
|
...remoteErrorLines(result),
|
|
"",
|
|
`NEXT ${stringValue(record(disclosure.next).full)}`,
|
|
]);
|
|
}
|
|
|
|
export function renderPlan(result: Record<string, unknown>): RenderedCliResult {
|
|
const config = record(result.config);
|
|
const target = record(config.target);
|
|
const app = record(config.app);
|
|
const migration = record(config.migration);
|
|
const policy = arrayRecords(result.policy);
|
|
const failed = policy.filter((item) => item.ok === false);
|
|
const next = record(result.next);
|
|
return rendered(result, "platform-infra gitea plan", [
|
|
"PLATFORM-INFRA GITEA PLAN",
|
|
...table(["FIELD", "VALUE", "DETAIL", "VALUE"], [
|
|
["TARGET", stringValue(target.id), "route", stringValue(target.route)],
|
|
["NAMESPACE", stringValue(target.namespace), "role", stringValue(target.role)],
|
|
["IMAGE", stringValue(app.image), "replicas", stringValue(app.replicas)],
|
|
["SERVICE", `${stringValue(app.serviceName)}:${stringValue(record(app.service).httpPort)}`, "dns", serviceDnsFromObjects(app, target)],
|
|
["WEBUI", stringValue(record(app.publicExposure).publicBaseUrl), "internalGit", serviceDnsFromObjects(app, target)],
|
|
["ACTIONS", boolText(app.actionsEnabled), "registrationDisabled", boolText(app.registrationDisabled)],
|
|
["MIGRATION", stringValue(migration.role), "replaces", stringValue(migration.replaces)],
|
|
["POLICY", failed.length === 0 ? "ok" : `failed=${failed.length}`, "valuesPrinted", "false"],
|
|
]),
|
|
"",
|
|
"NEXT",
|
|
` status: ${stringValue(next.status)}`,
|
|
` validate: ${stringValue(next.validate)}`,
|
|
` mirror-status: ${stringValue(next.mirrorStatus)}`,
|
|
` webhook-status: ${stringValue(next.webhookStatus)}`,
|
|
` fix-automatic-delivery: ${stringValue(next.fixAutomaticDelivery)}`,
|
|
"",
|
|
"Boundary: Gitea is internal ClusterIP source authority for GH-1560; CI trigger authority is Pipelines-as-Code, not Gitea Actions/act_runner.",
|
|
"Disclosure: Secret values are not printed; this stage does not create runner credentials.",
|
|
]);
|
|
}
|
|
|
|
export function renderApply(result: Record<string, unknown>): RenderedCliResult {
|
|
const target = record(result.target);
|
|
const payload = record(result.payloadTransport);
|
|
const remote = record(result.remote);
|
|
const steps = record(remote.steps);
|
|
const serverDryRunStep = record(steps.serverDryRun);
|
|
const frpcStep = record(steps.frpcSecret);
|
|
const frpcCleanupStep = record(steps.frpcCleanup);
|
|
const webhookSecretStep = record(steps.webhookSyncSecret);
|
|
const applyStep = record(steps.apply);
|
|
const rolloutStep = record(steps.rollout);
|
|
const frpcRolloutStep = record(steps.frpcRollout);
|
|
const webhookRolloutStep = record(steps.webhookSyncRollout);
|
|
const caddy = record(result.pk01Caddy);
|
|
return rendered(result, "platform-infra gitea apply", [
|
|
"PLATFORM-INFRA GITEA APPLY",
|
|
...table(["TARGET", "NAMESPACE", "MODE", "OK"], [[stringValue(target.id), stringValue(target.namespace), stringValue(result.mode), boolText(result.ok)]]),
|
|
...table(["TRANSPORT", "BYTES", "MATERIALIZATION", "VALUES"], [[stringValue(payload.kind), stringValue(payload.totalBytes), stringValue(payload.materialization), stringValue(payload.valuesPrinted)]]),
|
|
"",
|
|
"STEPS",
|
|
...table(["STEP", "EXIT", "DETAIL"], [
|
|
["serverDryRun", stringValue(serverDryRunStep.exitCode), compactLine(stringValue(serverDryRunStep.stderrTail, stringValue(serverDryRunStep.stdoutTail)))],
|
|
["frpc-secret", stringValue(frpcStep.exitCode), compactLine(stringValue(frpcStep.stderrTail, stringValue(frpcStep.stdoutTail)))],
|
|
["frpc-cleanup", stringValue(frpcCleanupStep.exitCode), compactLine(stringValue(frpcCleanupStep.stderrTail, stringValue(frpcCleanupStep.stdoutTail)))],
|
|
["webhook-secret", stringValue(webhookSecretStep.exitCode), compactLine(stringValue(webhookSecretStep.stderrTail, stringValue(webhookSecretStep.stdoutTail)))],
|
|
["apply", stringValue(applyStep.exitCode), compactLine(stringValue(applyStep.stderrTail, stringValue(applyStep.stdoutTail)))],
|
|
["rollout", stringValue(rolloutStep.exitCode), compactLine(stringValue(rolloutStep.stderrTail, stringValue(rolloutStep.stdoutTail)))],
|
|
["frpc-rollout", stringValue(frpcRolloutStep.exitCode), compactLine(stringValue(frpcRolloutStep.stderrTail, stringValue(frpcRolloutStep.stdoutTail)))],
|
|
["webhook-rollout", stringValue(webhookRolloutStep.exitCode), compactLine(stringValue(webhookRolloutStep.stderrTail, stringValue(webhookRolloutStep.stdoutTail)))],
|
|
["pk01-caddy", stringValue(caddy.exitCode ?? (caddy.ok === false ? 1 : 0)), compactLine(stringValue(caddy.error, stringValue(caddy.status, stringValue(caddy.reason))))],
|
|
]),
|
|
...remoteErrorLines(result),
|
|
"",
|
|
`NEXT bun scripts/cli.ts platform-infra gitea status --target ${stringValue(target.id)}`,
|
|
]);
|
|
}
|
|
|
|
export function renderStatus(result: Record<string, unknown>): RenderedCliResult {
|
|
const summary = record(result.summary);
|
|
const statefulSet = record(summary.statefulSet);
|
|
const service = record(summary.service);
|
|
const networkPolicy = record(summary.networkPolicy);
|
|
const pods = arrayRecords(summary.pods).map((pod) => [stringValue(pod.name), stringValue(pod.phase), boolText(pod.ready), stringValue(pod.restarts)]);
|
|
const pvcs = arrayRecords(summary.pvcs).map((pvc) => [stringValue(pvc.name), stringValue(pvc.phase), stringValue(pvc.capacity)]);
|
|
return rendered(result, "platform-infra gitea status", [
|
|
"PLATFORM-INFRA GITEA STATUS",
|
|
...table(["TARGET", "ROUTE", "NAMESPACE", "READY"], [[stringValue(summary.target), stringValue(summary.route), stringValue(summary.namespace), boolText(summary.ready)]]),
|
|
"",
|
|
"CONTROL",
|
|
...table(["CHECK", "VALUE", "DETAIL"], [
|
|
["statefulSet", `${stringValue(statefulSet.readyReplicas)}/${stringValue(statefulSet.desired)}`, stringValue(statefulSet.name)],
|
|
["service", stringValue(service.type), stringValue(summary.serviceDns)],
|
|
["endpoints", boolText(summary.endpointsReady), stringValue(service.clusterIP)],
|
|
["allow-all", boolText(networkPolicy.allowAllPresent), "NetworkPolicy"],
|
|
]),
|
|
"",
|
|
"PODS",
|
|
...(pods.length === 0 ? ["-"] : table(["POD", "PHASE", "READY", "RESTARTS"], pods)),
|
|
"",
|
|
"PVCS",
|
|
...(pvcs.length === 0 ? ["-"] : table(["PVC", "PHASE", "CAPACITY"], pvcs)),
|
|
...remoteErrorLines(result),
|
|
"",
|
|
`NEXT bun scripts/cli.ts platform-infra gitea validate --target ${stringValue(summary.target)}`,
|
|
]);
|
|
}
|
|
|
|
function remoteErrorLines(result: Record<string, unknown>): string[] {
|
|
if (result.ok !== false) return [];
|
|
const remote = record(result.remote);
|
|
const exitCode = stringValue(remote.exitCode);
|
|
const stderr = compactLine(stringValue(remote.stderrTail));
|
|
const stdout = compactLine(stringValue(remote.stdoutTail));
|
|
const detail = stderr !== "-" ? stderr : stdout;
|
|
return ["", "ERROR", ...table(["EXIT", "DETAIL"], [[exitCode, detail]])];
|
|
}
|
|
|
|
function compactTail(value: string): string {
|
|
const line = value.split(/\r?\n/u).map((item) => item.trim()).filter(Boolean).at(-1) ?? "";
|
|
return line.length > 96 ? `${line.slice(0, 93)}...` : line;
|
|
}
|
|
|
|
function rendered(result: Record<string, unknown>, command: string, lines: string[]): RenderedCliResult {
|
|
return { ok: result.ok !== false, command, renderedText: lines.join("\n"), contentType: "text/plain" };
|
|
}
|
|
|
|
function serviceDnsFromObjects(app: Record<string, unknown>, target: Record<string, unknown>): string {
|
|
const service = record(app.service);
|
|
return `${stringValue(app.serviceName)}.${stringValue(target.namespace)}.svc.cluster.local:${stringValue(service.httpPort)}`;
|
|
}
|
|
|
|
function record(value: unknown): Record<string, unknown> {
|
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : {};
|
|
}
|
|
|
|
function arrayRecords(value: unknown): Record<string, unknown>[] {
|
|
return Array.isArray(value)
|
|
? value.filter((item) => typeof item === "object" && item !== null && !Array.isArray(item)) as Record<string, unknown>[]
|
|
: [];
|
|
}
|
|
|
|
function stringValue(value: unknown, fallback = "-"): string {
|
|
if (typeof value === "string" && value.length > 0) return value;
|
|
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
return fallback;
|
|
}
|
|
|
|
function boolText(value: unknown): string {
|
|
return value === true ? "true" : "false";
|
|
}
|
|
|
|
function compactLine(value: string): string {
|
|
const trimmed = value.replace(/\s+/gu, " ").trim();
|
|
return trimmed.length > 0 ? trimmed.slice(0, 220) : "-";
|
|
}
|
|
|
|
function table(headers: string[], rows: string[][]): string[] {
|
|
const widths = headers.map((header, index) => Math.max(header.length, ...rows.map((row) => (row[index] ?? "").length)));
|
|
const format = (row: string[]) => row.map((cell, index) => cell.padEnd(widths[index])).join(" ");
|
|
return [format(headers), format(headers.map((header, index) => "-".repeat(Math.max(header.length, widths[index])))), ...rows.map(format)];
|
|
}
|