| import { loadConfig } from "../config/config.js"; |
| import { resolveCommitHash } from "../infra/git-commit.js"; |
| import { visibleWidth } from "../terminal/ansi.js"; |
| import { isRich, theme } from "../terminal/theme.js"; |
| import { hasRootVersionAlias } from "./argv.js"; |
| import { pickTagline, type TaglineMode, type TaglineOptions } from "./tagline.js"; |
|
|
| type BannerOptions = TaglineOptions & { |
| argv?: string[]; |
| commit?: string | null; |
| columns?: number; |
| richTty?: boolean; |
| }; |
|
|
| let bannerEmitted = false; |
|
|
| const graphemeSegmenter = |
| typeof Intl !== "undefined" && "Segmenter" in Intl |
| ? new Intl.Segmenter(undefined, { granularity: "grapheme" }) |
| : null; |
|
|
| function splitGraphemes(value: string): string[] { |
| if (!graphemeSegmenter) { |
| return Array.from(value); |
| } |
| try { |
| return Array.from(graphemeSegmenter.segment(value), (seg) => seg.segment); |
| } catch { |
| return Array.from(value); |
| } |
| } |
|
|
| const hasJsonFlag = (argv: string[]) => |
| argv.some((arg) => arg === "--json" || arg.startsWith("--json=")); |
|
|
| const hasVersionFlag = (argv: string[]) => |
| argv.some((arg) => arg === "--version" || arg === "-V") || hasRootVersionAlias(argv); |
|
|
| function parseTaglineMode(value: unknown): TaglineMode | undefined { |
| if (value === "random" || value === "default" || value === "off") { |
| return value; |
| } |
| return undefined; |
| } |
|
|
| function resolveTaglineMode(options: BannerOptions): TaglineMode | undefined { |
| const explicit = parseTaglineMode(options.mode); |
| if (explicit) { |
| return explicit; |
| } |
| try { |
| return parseTaglineMode(loadConfig().cli?.banner?.taglineMode); |
| } catch { |
| |
| return undefined; |
| } |
| } |
|
|
| export function formatCliBannerLine(version: string, options: BannerOptions = {}): string { |
| const commit = |
| options.commit ?? resolveCommitHash({ env: options.env, moduleUrl: import.meta.url }); |
| const commitLabel = commit ?? "unknown"; |
| const tagline = pickTagline({ ...options, mode: resolveTaglineMode(options) }); |
| const rich = options.richTty ?? isRich(); |
| const title = "π€ OpenSkynet"; |
| const prefix = "π€ "; |
| const columns = options.columns ?? process.stdout.columns ?? 120; |
| const plainBaseLine = `${title} ${version} (${commitLabel})`; |
| const plainFullLine = tagline ? `${plainBaseLine} β ${tagline}` : plainBaseLine; |
| const fitsOnOneLine = visibleWidth(plainFullLine) <= columns; |
| if (rich) { |
| if (fitsOnOneLine) { |
| if (!tagline) { |
| return `${theme.heading(title)} ${theme.info(version)} ${theme.muted(`(${commitLabel})`)}`; |
| } |
| return `${theme.heading(title)} ${theme.info(version)} ${theme.muted( |
| `(${commitLabel})`, |
| )} ${theme.muted("β")} ${theme.accentDim(tagline)}`; |
| } |
| const line1 = `${theme.heading(title)} ${theme.info(version)} ${theme.muted( |
| `(${commitLabel})`, |
| )}`; |
| if (!tagline) { |
| return line1; |
| } |
| const line2 = `${" ".repeat(prefix.length)}${theme.accentDim(tagline)}`; |
| return `${line1}\n${line2}`; |
| } |
| if (fitsOnOneLine) { |
| return plainFullLine; |
| } |
| const line1 = plainBaseLine; |
| if (!tagline) { |
| return line1; |
| } |
| const line2 = `${" ".repeat(prefix.length)}${tagline}`; |
| return `${line1}\n${line2}`; |
| } |
|
|
| const LOBSTER_ASCII = [ |
| "ββββββββββββββββββββββββββββββββββββββββββββββββββββ", |
| "ββββββββββββββββββββββββββββββββββββββββββββββββββββ", |
| "ββββββββββββββββββββββββββββββββββββββββββββββββββββ", |
| "ββββββββββββββββββββββββββββββββββββββββββββββββββββ", |
| "ββββββββββββββββββββββββββββββββββββββββββββββββββββ", |
| " π€ OPENSKYNET π€ ", |
| " ", |
| ]; |
|
|
| export function formatCliBannerArt(options: BannerOptions = {}): string { |
| const rich = options.richTty ?? isRich(); |
| if (!rich) { |
| return LOBSTER_ASCII.join("\n"); |
| } |
|
|
| const colorChar = (ch: string) => { |
| if (ch === "β") { |
| return theme.accentBright(ch); |
| } |
| if (ch === "β") { |
| return theme.accentDim(ch); |
| } |
| if (ch === "β") { |
| return theme.accent(ch); |
| } |
| return theme.muted(ch); |
| }; |
|
|
| const colored = LOBSTER_ASCII.map((line) => { |
| if (line.includes("OPENSKYNET")) { |
| return ( |
| theme.muted(" ") + |
| theme.accent("π€") + |
| theme.info(" OPENSKYNET ") + |
| theme.accent("π€") |
| ); |
| } |
| return splitGraphemes(line).map(colorChar).join(""); |
| }); |
|
|
| return colored.join("\n"); |
| } |
|
|
| export function emitCliBanner(version: string, options: BannerOptions = {}) { |
| if (bannerEmitted) { |
| return; |
| } |
| const argv = options.argv ?? process.argv; |
| if (!process.stdout.isTTY) { |
| return; |
| } |
| if (hasJsonFlag(argv)) { |
| return; |
| } |
| if (hasVersionFlag(argv)) { |
| return; |
| } |
| const line = formatCliBannerLine(version, options); |
| process.stdout.write(`\n${line}\n\n`); |
| bannerEmitted = true; |
| } |
|
|
| export function hasEmittedCliBanner(): boolean { |
| return bannerEmitted; |
| } |
|
|