UNPKG

@settlemint/sdk-utils

Version:

Shared utilities and helper functions for SettleMint SDK modules

441 lines (428 loc) 12.7 kB
import { greenBright, inverse, magentaBright, redBright, whiteBright, yellowBright } from "yoctocolors"; import { spawn } from "node:child_process"; import isInCi from "is-in-ci"; import yoctoSpinner from "yocto-spinner"; import { Table } from "console-table-printer"; //#region src/terminal/should-print.ts /** * Returns true if the terminal should print, false otherwise. * @returns true if the terminal should print, false otherwise. */ function shouldPrint() { return process.env.SETTLEMINT_DISABLE_TERMINAL !== "true"; } //#endregion //#region src/terminal/ascii.ts /** * Prints the SettleMint ASCII art logo to the console in magenta color. * Used for CLI branding and visual identification. * * @example * import { ascii } from "@settlemint/sdk-utils/terminal"; * * // Prints the SettleMint logo * ascii(); */ const ascii = () => { if (!shouldPrint()) { return; } console.log(magentaBright(` _________ __ __ .__ _____ .__ __ / _____/ _____/ |__/ |_| | ____ / \\ |__| _____/ |_ \\_____ \\_/ __ \\ __\\ __\\ | _/ __ \\ / \\ / \\| |/ \\ __\\ / \\ ___/| | | | | |_\\ ___// Y \\ | | \\ | /_________/\\_____>__| |__| |____/\\_____>____|____/__|___|__/__| `)); }; //#endregion //#region src/logging/mask-tokens.ts /** * Masks sensitive SettleMint tokens in output text by replacing them with asterisks. * Handles personal access tokens (PAT), application access tokens (AAT), and service account tokens (SAT). * * @param output - The text string that may contain sensitive tokens * @returns The text with any sensitive tokens masked with asterisks * @example * import { maskTokens } from "@settlemint/sdk-utils/terminal"; * * // Masks a token in text * const masked = maskTokens("Token: sm_pat_****"); // "Token: ***" */ const maskTokens = (output) => { return output.replace(/sm_(pat|aat|sat)_[0-9a-zA-Z]+/g, "***"); }; //#endregion //#region src/terminal/cancel.ts /** * Error class used to indicate that the operation was cancelled. * This error is used to signal that the operation should be aborted. */ var CancelError = class extends Error {}; /** * Displays an error message in red inverse text and throws a CancelError. * Used to terminate execution with a visible error message. * Any sensitive tokens in the message are masked before display. * * @param msg - The error message to display * @returns never - Function does not return as it throws an error * @example * import { cancel } from "@settlemint/sdk-utils/terminal"; * * // Exits process with error message * cancel("An error occurred"); */ const cancel = (msg) => { console.log(""); console.log(inverse(redBright(maskTokens(msg)))); console.log(""); throw new CancelError(msg); }; //#endregion //#region src/terminal/execute-command.ts /** * Error class for command execution errors * @extends Error */ var CommandError = class extends Error { /** * Constructs a new CommandError * @param message - The error message * @param code - The exit code of the command * @param output - The output of the command */ constructor(message, code, output) { super(message); this.code = code; this.output = output; } }; /** * Executes a command with the given arguments in a child process. * Pipes stdin to the child process and captures stdout/stderr output. * Masks any sensitive tokens in the output before displaying or returning. * * @param command - The command to execute * @param args - Array of arguments to pass to the command * @param options - Options for customizing command execution * @returns Array of output strings from stdout and stderr * @throws {CommandError} If the process fails to start or exits with non-zero code * @example * import { executeCommand } from "@settlemint/sdk-utils/terminal"; * * // Execute git clone * await executeCommand("git", ["clone", "repo-url"]); * * // Execute silently * await executeCommand("npm", ["install"], { silent: true }); */ async function executeCommand(command, args, options) { const { silent,...spawnOptions } = options ?? {}; const child = spawn(command, args, { ...spawnOptions, env: { ...process.env, ...options?.env } }); process.stdin.pipe(child.stdin); const output = []; return new Promise((resolve, reject) => { child.stdout.on("data", (data) => { const maskedData = maskTokens(data.toString()); if (!silent) { process.stdout.write(maskedData); } output.push(maskedData); }); child.stderr.on("data", (data) => { const maskedData = maskTokens(data.toString()); if (!silent) { process.stderr.write(maskedData); } output.push(maskedData); }); child.on("error", (err) => { process.stdin.unpipe(child.stdin); reject(new CommandError(err.message, "code" in err && typeof err.code === "number" ? err.code : 1, output)); }); child.on("close", (code) => { process.stdin.unpipe(child.stdin); if (code === 0 || code === null || code === 143) { resolve(output); return; } reject(new CommandError(`Command "${command}" exited with code ${code}`, code, output)); }); }); } //#endregion //#region src/terminal/intro.ts /** * Displays an introductory message in magenta text with padding. * Any sensitive tokens in the message are masked before display. * * @param msg - The message to display as introduction * @example * import { intro } from "@settlemint/sdk-utils/terminal"; * * // Display intro message * intro("Starting deployment..."); */ const intro = (msg) => { if (!shouldPrint()) { return; } console.log(""); console.log(magentaBright(maskTokens(msg))); console.log(""); }; //#endregion //#region src/terminal/note.ts /** * Displays a note message with optional warning level formatting. * Regular notes are displayed in normal text, while warnings are shown in yellow. * Any sensitive tokens in the message are masked before display. * * @param message - The message to display as a note * @param level - The note level: "info" (default) or "warn" for warning styling * @example * import { note } from "@settlemint/sdk-utils/terminal"; * * // Display info note * note("Operation completed successfully"); * * // Display warning note * note("Low disk space remaining", "warn"); */ const note = (message, level = "info") => { if (!shouldPrint()) { return; } const maskedMessage = maskTokens(message); console.log(""); if (level === "warn") { console.warn(yellowBright(maskedMessage)); return; } console.log(maskedMessage); }; //#endregion //#region src/terminal/list.ts /** * Displays a list of items in a formatted manner, supporting nested items. * * @param title - The title of the list * @param items - The items to display, can be strings or arrays for nested items * @returns The formatted list * @example * import { list } from "@settlemint/sdk-utils/terminal"; * * // Simple list * list("Use cases", ["use case 1", "use case 2", "use case 3"]); * * // Nested list * list("Providers", [ * "AWS", * ["us-east-1", "eu-west-1"], * "Azure", * ["eastus", "westeurope"] * ]); */ function list(title, items) { const formatItems = (items$1) => { return items$1.map((item) => { if (Array.isArray(item)) { return item.map((subItem) => ` • ${subItem}`).join("\n"); } return ` • ${item}`; }).join("\n"); }; return note(`${title}:\n\n${formatItems(items)}`); } //#endregion //#region src/terminal/outro.ts /** * Displays a closing message in green inverted text with padding. * Any sensitive tokens in the message are masked before display. * * @param msg - The message to display as conclusion * @example * import { outro } from "@settlemint/sdk-utils/terminal"; * * // Display outro message * outro("Deployment completed successfully!"); */ const outro = (msg) => { if (!shouldPrint()) { return; } console.log(""); console.log(inverse(greenBright(maskTokens(msg)))); console.log(""); }; //#endregion //#region src/terminal/spinner.ts /** * Error class used to indicate that the spinner operation failed. * This error is used to signal that the operation should be aborted. */ var SpinnerError = class extends Error { constructor(message, originalError) { super(message); this.originalError = originalError; this.name = "SpinnerError"; } }; /** * Displays a loading spinner while executing an async task. * Shows progress with start/stop messages and handles errors. * Spinner is disabled in CI environments. * * @param options - Configuration options for the spinner * @returns The result from the executed task * @throws Will exit process with code 1 if task fails * @example * import { spinner } from "@settlemint/sdk-utils/terminal"; * * // Show spinner during async task * const result = await spinner({ * startMessage: "Deploying...", * task: async () => { * // Async work here * return "success"; * }, * stopMessage: "Deployed successfully!" * }); */ const spinner = async (options) => { const handleError = (error) => { const errorMessage = maskTokens(error.message); note(redBright(`${errorMessage}\n\n${error.stack}`)); throw new SpinnerError(errorMessage, error); }; if (isInCi || !shouldPrint()) { try { return await options.task(); } catch (err) { return handleError(err); } } const spinner$1 = yoctoSpinner({ stream: process.stdout }).start(options.startMessage); try { const result = await options.task(spinner$1); spinner$1.success(options.stopMessage); await new Promise((resolve) => process.nextTick(resolve)); return result; } catch (err) { spinner$1.error(redBright(`${options.startMessage} --> Error!`)); return handleError(err); } }; //#endregion //#region src/string.ts /** * Capitalizes the first letter of a string. * * @param val - The string to capitalize * @returns The input string with its first letter capitalized * * @example * import { capitalizeFirstLetter } from "@settlemint/sdk-utils"; * * const capitalized = capitalizeFirstLetter("hello"); * // Returns: "Hello" */ function capitalizeFirstLetter(val) { return String(val).charAt(0).toUpperCase() + String(val).slice(1); } /** * Converts a camelCase string to a human-readable string. * * @param s - The camelCase string to convert * @returns The human-readable string * * @example * import { camelCaseToWords } from "@settlemint/sdk-utils"; * * const words = camelCaseToWords("camelCaseString"); * // Returns: "Camel Case String" */ function camelCaseToWords(s) { const result = s.replace(/([a-z])([A-Z])/g, "$1 $2"); const withSpaces = result.replace(/([A-Z])([a-z])/g, " $1$2"); const capitalized = capitalizeFirstLetter(withSpaces); return capitalized.replace(/\s+/g, " ").trim(); } /** * Replaces underscores and hyphens with spaces. * * @param s - The string to replace underscores and hyphens with spaces * @returns The input string with underscores and hyphens replaced with spaces * * @example * import { replaceUnderscoresAndHyphensWithSpaces } from "@settlemint/sdk-utils"; * * const result = replaceUnderscoresAndHyphensWithSpaces("Already_Spaced-Second"); * // Returns: "Already Spaced Second" */ function replaceUnderscoresAndHyphensWithSpaces(s) { return s.replace(/[-_]/g, " "); } /** * Truncates a string to a maximum length and appends "..." if it is longer. * * @param value - The string to truncate * @param maxLength - The maximum length of the string * @returns The truncated string or the original string if it is shorter than the maximum length * * @example * import { truncate } from "@settlemint/sdk-utils"; * * const truncated = truncate("Hello, world!", 10); * // Returns: "Hello, wor..." */ function truncate(value, maxLength) { if (value.length <= maxLength) { return value; } return `${value.slice(0, maxLength)}...`; } //#endregion //#region src/terminal/table.ts /** * Displays data in a formatted table in the terminal. * * @param title - Title to display above the table * @param data - Array of objects to display in table format * @example * import { table } from "@settlemint/sdk-utils/terminal"; * * const data = [ * { name: "Item 1", value: 100 }, * { name: "Item 2", value: 200 } * ]; * * table("My Table", data); */ function table(title, data) { if (!shouldPrint()) { return; } note(title); if (!data || data.length === 0) { note("No data to display"); return; } const columnKeys = Object.keys(data[0]); const table$1 = new Table({ columns: columnKeys.map((key) => ({ name: key, title: whiteBright(camelCaseToWords(key)), alignment: "left" })) }); table$1.addRows(data); table$1.printTable(); } //#endregion export { CancelError, CommandError, SpinnerError, ascii, cancel, executeCommand, intro, list, maskTokens, note, outro, spinner, table }; //# sourceMappingURL=terminal.js.map