UNPKG

@supernovaio/cli

Version:

Supernova.io Command Line Interface

216 lines (214 loc) 8.92 kB
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="44d29699-da7d-5c61-8ed1-0d3d123e2c9b")}catch(e){}}(); import path from "node:path"; import { stripVTControlCharacters } from "node:util"; import { pathToFileURL } from "node:url"; import terminalLink from "terminal-link"; import { SETUP_INTRO_FRAME_INNER_WIDTH } from "./constants.js"; const SETUP_FRAME_INNER_MIN = 50; const DEFAULT_INNER_PADDING = 2; function innerVerticalLineCount(horizontalPad) { if (horizontalPad < 1) { return 0; } return Math.max(1, horizontalPad - 1); } const HEADER_ART_BLANK_LINES = 1; export function resolveFrameContentColumnWidth(input) { const cols = input.columns ?? process.stdout.columns ?? 100; const p = input.innerSidePadding; const margin = 2 + 2 * p; const maxByTerm = Math.max(SETUP_FRAME_INNER_MIN, cols - margin); const asIntro = Math.min(SETUP_INTRO_FRAME_INNER_WIDTH, maxByTerm); return Math.max(input.minContentWidth ?? SETUP_FRAME_INNER_MIN, asIntro); } export function colorize(text, tone) { if (tone === "none") return text; const codes = { amber: "\u001B[38;2;255;215;120m", bold: "\u001B[1m", blue: "\u001B[34m", blueBold: "\u001B[1;34m", blueDark: "\u001B[38;2;0;64;150m", cyan: "\u001B[36m", gray: "\u001B[90m", green: "\u001B[32m", greenBold: "\u001B[1;32m", none: "", yellow: "\u001B[33m", yellowBold: "\u001B[1;38;2;200;140;20m", }; return `${codes[tone]}${text}\u001B[0m`; } export function headline(text) { return colorize(text, "bold"); } export function muted(text) { return colorize(text, "gray"); } export function success(text) { return `${colorize("✓", "green")} ${text}`; } export function warning(text) { return `${colorize("!", "yellow")} ${text}`; } export function emphasis(text) { return colorize(text, "blue"); } export function renderStepHeader(title) { return [`${colorize("■", "blue")} ${headline(`✨ ${title}`)}`, muted("─".repeat(Math.max(20, title.length + 4)))]; } export function formatFullSnapshotPathLink(snapshotRoot) { return formatLocalPathLink(snapshotRoot); } export function formatLocalPathLink(targetPath) { const normalizedPath = path.resolve(targetPath); const fileUrl = pathToFileURL(normalizedPath).href; return terminalLink(normalizedPath, fileUrl, { fallback: text => text }); } function centerToVisibleWidth(text, targetWidth) { const w = visibleWidth(text); if (w >= targetWidth) { return text; } const pad = targetWidth - w; const left = Math.floor(pad / 2); const right = pad - left; return `${" ".repeat(left)}${text}${" ".repeat(right)}`; } function wrapFramedPanel(leadingRows, lines, maxTerminalWidth, lineTone, extras, innerSidePadding, targetContentWidth) { const innerPadding = innerSidePadding; const wrappedLines = lines.flatMap((line, lineIndex) => { const tone = lineTone ? lineTone(line, lineIndex) : lineIsListItem(line) ? "gray" : "none"; return wrapLine(line, maxTerminalWidth).map(segment => ({ segment, tone })); }); const art = extras?.headerArt; const artMaxW = art && art.length > 0 ? Math.max(...art.map(line => visibleWidth(line))) : 0; const minInnerForArt = artMaxW > 0 ? Math.max(0, artMaxW - 2 * innerPadding - 2) : 0; const width = Math.max(targetContentWidth, minInnerForArt, 30); const allRows = reflowRowsToFrameWidth([...leadingRows, ...wrappedLines], width); const betweenCorners = width + 2 * innerPadding; const horiz = "\u2500".repeat(betweenCorners); const topBorderPlain = `\u250C${horiz}\u2510`; const frameDisplayWidth = visibleWidth(topBorderPlain); const topBorder = colorize(topBorderPlain, "blue"); const bottomBorder = colorize(`\u2514${horiz}\u2518`, "blue"); const pad = " ".repeat(innerPadding); const row = (value = "", tone = "none") => `${colorize("\u2502", "blue")}${pad}${colorize(padToVisibleWidth(value, width), tone)}${pad}${colorize("\u2502", "blue")}`; const bodyText = allRows.map(item => row(item.segment, item.tone)); const vCount = innerVerticalLineCount(innerPadding); const vPad = Array.from({ length: vCount }, () => row("", "none")); const body = [...vPad, ...bodyText, ...vPad]; const headerArtTone = extras?.headerArtTone ?? "blue"; const artColored = art && art.length > 0 ? art.map(line => colorize(centerToVisibleWidth(line, frameDisplayWidth), headerArtTone)) : []; const artPrefix = artColored.length > 0 ? Array.from({ length: HEADER_ART_BLANK_LINES }, () => "") : []; const artSuffix = artColored.length > 0 ? Array.from({ length: HEADER_ART_BLANK_LINES }, () => "") : []; return [...artPrefix, ...artColored, ...artSuffix, topBorder, ...body, bottomBorder].join("\n"); } function panelFraming(options) { const innerSidePadding = options?.innerSidePadding ?? DEFAULT_INNER_PADDING; return { contentColumnWidth: resolveFrameContentColumnWidth({ minContentWidth: options?.minContentWidth, innerSidePadding, }), innerSidePadding, }; } export function renderPanel(title, lines, options) { const { contentColumnWidth, innerSidePadding } = panelFraming(options); const titleTone = options?.titleTone ?? "bold"; const wrappedTitle = wrapLine(title, contentColumnWidth).map(segment => ({ segment, tone: titleTone, })); return wrapFramedPanel(wrappedTitle, lines, contentColumnWidth, options?.lineTone, { headerArt: options?.headerArt, headerArtTone: options?.headerArtTone, }, innerSidePadding, contentColumnWidth); } export function renderFramedLines(lines, options) { const { contentColumnWidth, innerSidePadding } = panelFraming(options); return wrapFramedPanel([], lines, contentColumnWidth, options?.lineTone, { headerArt: options?.headerArt, headerArtTone: options?.headerArtTone, }, innerSidePadding, contentColumnWidth); } function lineIsListItem(line) { const trimmed = line.trimStart(); return trimmed.startsWith("- ") || /^\d+\.\s/.test(trimmed); } function lineHasTerminalHyperlink(line) { return line.includes("\u001B]8"); } function reflowRowsToFrameWidth(rows, maxContentWidth) { return rows.flatMap(row => { if (visibleWidth(row.segment) <= maxContentWidth) { return [row]; } if (lineHasTerminalHyperlink(row.segment)) { const plain = stripVTControlCharacters(row.segment); return wrapTextToWidth(plain, maxContentWidth).map(s => ({ segment: s, tone: row.tone })); } return wrapTextToWidth(row.segment, maxContentWidth).map(s => ({ segment: s, tone: row.tone })); }); } function wrapLine(line, width) { if (visibleWidth(line) <= width) { return [line]; } if (!line.trim()) { return [""]; } if (lineHasTerminalHyperlink(line)) { return [line]; } return wrapTextToWidth(line, width); } function wrapTextToWidth(line, width) { if (visibleWidth(line) <= width) { return [line]; } if (!line.trim()) { return [""]; } const parts = []; let remaining = line; while (remaining.length > width) { const chunk = remaining.slice(0, width + 1); const breakAt = Math.max(chunk.lastIndexOf(" "), chunk.lastIndexOf("/"), chunk.lastIndexOf("\\"), chunk.lastIndexOf("-")); const splitIndex = breakAt > 12 ? breakAt : width; parts.push(remaining.slice(0, splitIndex).trimEnd()); remaining = remaining.slice(splitIndex).trimStart(); } if (remaining.length > 0) { parts.push(remaining); } return parts; } function padToVisibleWidth(value, width) { const padding = Math.max(0, width - visibleWidth(value)); return `${value}${" ".repeat(padding)}`; } function visibleWidth(value) { const strippedValue = stripVTControlCharacters(value); let width = 0; for (const char of Array.from(strippedValue)) { if (char === "\u200D" || char === "\uFE0E" || char === "\uFE0F") { continue; } if (/[\p{Mark}\p{Control}]/u.test(char)) { continue; } width += isWideCharacter(char) ? 2 : 1; } return width; } function isWideCharacter(char) { if (char === "\u2139" || char === "\u23F1") { return true; } return /[\p{Extended_Pictographic}\u1100-\u115F\u2329\u232A\u2E80-\uA4CF\uAC00-\uD7A3\uF900-\uFAFF\uFE10-\uFE19\uFE30-\uFE6F\uFF00-\uFF60\uFFE0-\uFFE6]/u.test(char); } //# sourceMappingURL=ui.js.map //# debugId=44d29699-da7d-5c61-8ed1-0d3d123e2c9b