@supernovaio/cli
Version:
Supernova.io Command Line Interface
216 lines (214 loc) • 8.92 kB
JavaScript
!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