192 lines
5.9 KiB
JavaScript
192 lines
5.9 KiB
JavaScript
/**
|
|
* Docker / startup terminal banner for Presenton.
|
|
* Renders a compact brand logo + startup status.
|
|
*/
|
|
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const RESET = "\x1b[0m";
|
|
const BOLD = "\x1b[1m";
|
|
const DIM = "\x1b[2m";
|
|
|
|
function fgRgb(r, g, b, text) {
|
|
return `\x1b[38;2;${r};${g};${b}m${text}${RESET}`;
|
|
}
|
|
|
|
function brand(text) {
|
|
return BOLD + fgRgb(138, 99, 255, text);
|
|
}
|
|
|
|
function accent(text) {
|
|
return fgRgb(184, 176, 255, text);
|
|
}
|
|
|
|
function muted(text) {
|
|
return DIM + fgRgb(160, 150, 210, text);
|
|
}
|
|
|
|
function styleAsciiArt(rawAscii) {
|
|
const lines = rawAscii.replace(/\r/g, "").split("\n");
|
|
const palette = [
|
|
[153, 108, 255],
|
|
[145, 103, 250],
|
|
[136, 98, 245],
|
|
[127, 92, 239],
|
|
[118, 86, 233],
|
|
];
|
|
|
|
return lines
|
|
.map((line, lineIdx) => {
|
|
const [r, g, b] = palette[lineIdx % palette.length];
|
|
return line.replace(/[^\s]/g, (ch) => fgRgb(r, g, b, ch));
|
|
})
|
|
.join("\n");
|
|
}
|
|
|
|
function loadAsciiBanner() {
|
|
const thisDir = path.dirname(fileURLToPath(import.meta.url));
|
|
const asciiPath = path.join(thisDir, "presenton-ascii.txt");
|
|
|
|
try {
|
|
const raw = fs.readFileSync(asciiPath, "utf8").trimEnd();
|
|
if (!raw) return "";
|
|
return styleAsciiArt(raw);
|
|
} catch {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
function loadPackageVersion() {
|
|
const thisDir = path.dirname(fileURLToPath(import.meta.url));
|
|
const packageJsonPath = path.join(thisDir, "..", "package.json");
|
|
|
|
try {
|
|
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
return typeof pkg.version === "string" ? pkg.version : "";
|
|
} catch {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/** Visible width (strips SGR sequences). */
|
|
function visLen(s) {
|
|
return s.replace(/\x1b\[[0-9;:]*m/g, "").length;
|
|
}
|
|
|
|
/** Pad styled fragment to fixed visible width. */
|
|
function padVis(styled, width) {
|
|
return styled + " ".repeat(Math.max(0, width - visLen(styled)));
|
|
}
|
|
|
|
/**
|
|
* @param {object} [opts]
|
|
* @param {"development" | "production"} [opts.mode]
|
|
* @param {number} [opts.nextPort]
|
|
* @param {number} [opts.fastapiPort]
|
|
* @param {string} [opts.hostHttpPort] — host-published HTTP port (docker -p HOST:80). Default from env or "5001".
|
|
*/
|
|
export function printPresentonStartupBanner(opts = {}) {
|
|
const mode = opts.mode === "development" ? "development" : "production";
|
|
const nextPort = opts.nextPort ?? 3000;
|
|
const fastapiPort = opts.fastapiPort ?? 8000;
|
|
const version = opts.version ?? loadPackageVersion();
|
|
const hostHttpPort =
|
|
opts.hostHttpPort ??
|
|
process.env.PRESENTON_HTTP_HOST_PORT ??
|
|
process.env.PRESENTON_HOST_HTTP_PORT ??
|
|
process.env.PRESENTON_PUBLIC_PORT ??
|
|
"5001";
|
|
|
|
const nextUrl = `http://127.0.0.1:${nextPort}`;
|
|
const apiUrl = `http://127.0.0.1:${fastapiPort}`;
|
|
const publicUrl =
|
|
String(hostHttpPort) === "80"
|
|
? "http://127.0.0.1"
|
|
: `http://127.0.0.1:${hostHttpPort}`;
|
|
|
|
const iconBlock = loadAsciiBanner();
|
|
|
|
const title = [
|
|
"",
|
|
BOLD + fgRgb(138, 99, 255, " Open Source AI Presentation Generator"),
|
|
...(mode === "development"
|
|
? [
|
|
" " +
|
|
accent("Love the Project? ") +
|
|
brand("Star us on github: ") +
|
|
BOLD +
|
|
fgRgb(224, 218, 255, "https://github.com/presenton/presenton"),
|
|
]
|
|
: []),
|
|
muted(" ─────────────────────────────────────────────────────────"),
|
|
"",
|
|
].join("\n");
|
|
|
|
const W = 68;
|
|
const pipe = (inner) => brand(" ║") + inner + brand("║");
|
|
|
|
const boxTop = brand(
|
|
" ╔════════════════════════════════════════════════════════════════════╗",
|
|
);
|
|
const boxDivider = brand(
|
|
" ╠════════════════════════════════════════════════════════════════════╣",
|
|
);
|
|
const boxBottom = brand(
|
|
" ╚════════════════════════════════════════════════════════════════════╝",
|
|
);
|
|
|
|
const summaryLines =
|
|
mode === "development"
|
|
? [
|
|
pipe(padVis(" " + BOLD + "Routing summary" + RESET, W)),
|
|
boxDivider,
|
|
pipe(padVis(" " + muted("Mode: ") + mode, W)),
|
|
...(version
|
|
? [pipe(padVis(" " + muted("Version: ") + version, W))]
|
|
: []),
|
|
pipe(padVis(" " + accent("/ ") + muted("→ Next.js"), W)),
|
|
pipe(padVis(" " + accent("/api/v1/ ") + muted("→ FastAPI"), W)),
|
|
pipe(padVis(" " + muted("Next.js docker URL: ") + nextUrl, W)),
|
|
pipe(padVis(" " + muted("FastAPI docker URL: ") + apiUrl, W)),
|
|
pipe(
|
|
padVis(
|
|
" " +
|
|
muted("Public URL (Ctrl+Click to open): ") +
|
|
BOLD +
|
|
fgRgb(255, 255, 255, publicUrl),
|
|
W,
|
|
),
|
|
),
|
|
]
|
|
: [
|
|
pipe(padVis(" " + BOLD + "Application URL" + RESET, W)),
|
|
boxDivider,
|
|
pipe(padVis(" " + muted("Mode: ") + mode, W)),
|
|
...(version
|
|
? [pipe(padVis(" " + muted("Version: ") + version, W))]
|
|
: []),
|
|
pipe(
|
|
padVis(
|
|
" " +
|
|
muted("Open Presenton: ") +
|
|
BOLD +
|
|
fgRgb(255, 255, 255, publicUrl),
|
|
W,
|
|
),
|
|
),
|
|
];
|
|
|
|
const summary = [
|
|
boxTop,
|
|
...summaryLines,
|
|
boxBottom,
|
|
"",
|
|
" " + muted("Made with ❤️ by the Presenton team"),
|
|
].join("\n");
|
|
|
|
const bannerHeader = iconBlock ? `${iconBlock}\n` : "";
|
|
console.log("\n" + bannerHeader + title + summary);
|
|
}
|