UNPKG

@settlemint/sdk-utils

Version:

Shared utilities and helper functions for SettleMint SDK modules

586 lines (572 loc) 18 kB
//#region rolldown:runtime var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) { key = keys[i]; if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: ((k) => from[k]).bind(null, key), enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod)); //#endregion let yoctocolors = require("yoctocolors"); yoctocolors = __toESM(yoctocolors); let node_child_process = require("node:child_process"); node_child_process = __toESM(node_child_process); let is_in_ci = require("is-in-ci"); is_in_ci = __toESM(is_in_ci); let yocto_spinner = require("yocto-spinner"); yocto_spinner = __toESM(yocto_spinner); let console_table_printer = require("console-table-printer"); console_table_printer = __toESM(console_table_printer); //#region src/terminal/should-print.ts /** * Determines whether terminal output should be printed based on environment variables. * * **Environment Variable Precedence:** * 1. `SETTLEMINT_DISABLE_TERMINAL="true"` - Completely disables all terminal output (highest priority) * 2. `CLAUDECODE`, `REPL_ID`, or `AGENT` (any truthy value) - Enables quiet mode, suppressing info/debug/status messages * * **Quiet Mode Behavior:** * When quiet mode is active (Claude Code environments), this function returns `false` to suppress * informational output. However, warnings and errors are always displayed regardless of quiet mode, * as they are handled separately in the `note()` function with level-based filtering. * * @returns `true` if terminal output should be printed, `false` if suppressed */ function shouldPrint() { if (process.env.SETTLEMINT_DISABLE_TERMINAL === "true") { return false; } if (process.env.CLAUDECODE || process.env.REPL_ID || process.env.AGENT) { return false; } return 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((0, yoctocolors.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((0, yoctocolors.inverse)((0, yoctocolors.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; } }; /** * Checks if we're in quiet mode (Claude Code environment) */ function isQuietMode() { return !!(process.env.CLAUDECODE || process.env.REPL_ID || process.env.AGENT); } /** * 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. * In quiet mode (when CLAUDECODE, REPL_ID, or AGENT env vars are set), * output is suppressed unless the command errors out. * * @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 quietMode = isQuietMode(); const shouldSuppressOutput = quietMode ? silent !== false : !!silent; const child = (0, node_child_process.spawn)(command, args, { ...spawnOptions, env: { ...process.env, ...options?.env } }); process.stdin.pipe(child.stdin); const output = []; const stdoutOutput = []; const stderrOutput = []; return new Promise((resolve, reject) => { child.stdout.on("data", (data) => { const maskedData = maskTokens(data.toString()); if (!shouldSuppressOutput) { process.stdout.write(maskedData); } output.push(maskedData); stdoutOutput.push(maskedData); }); child.stderr.on("data", (data) => { const maskedData = maskTokens(data.toString()); if (!shouldSuppressOutput) { process.stderr.write(maskedData); } output.push(maskedData); stderrOutput.push(maskedData); }); const showErrorOutput = () => { if (quietMode && shouldSuppressOutput && output.length > 0) { if (stdoutOutput.length > 0) { process.stdout.write(stdoutOutput.join("")); } if (stderrOutput.length > 0) { process.stderr.write(stderrOutput.join("")); } } }; child.on("error", (err) => { process.stdin.unpipe(child.stdin); showErrorOutput(); 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; } showErrorOutput(); 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((0, yoctocolors.magentaBright)(maskTokens(msg))); console.log(""); }; //#endregion //#region src/terminal/note.ts /** * Applies color to a message if not already colored. * @param msg - The message to colorize * @param level - The severity level determining the color * @returns Colorized message (yellow for warnings, red for errors, unchanged for info) */ function colorize(msg, level) { if (msg.includes("\x1B[")) { return msg; } if (level === "warn") { return (0, yoctocolors.yellowBright)(msg); } if (level === "error") { return (0, yoctocolors.redBright)(msg); } return msg; } /** * Determines whether a message should be printed based on its level and quiet mode. * @param level - The severity level of the message * @returns true if the message should be printed, false otherwise */ function canPrint(level) { if (level !== "info") { return true; } return shouldPrint(); } /** * Prepares a message for display by converting Error objects and masking tokens. * @param value - The message string or Error object * @param level - The severity level (stack traces are included for errors) * @returns Masked message text, optionally with stack trace */ function prepareMessage(value, level) { let text; if (value instanceof Error) { text = value.message; if (level === "error" && value.stack) { text = `${text}\n\n${value.stack}`; } } else { text = value; } return maskTokens(text); } /** * Displays a note message with optional warning or error level formatting. * Regular notes are displayed in normal text, warnings are shown in yellow, and errors in red. * Any sensitive tokens in the message are masked before display. * Warnings and errors are always displayed, even in quiet mode (when CLAUDECODE, REPL_ID, or AGENT env vars are set). * When an Error object is provided with level "error", the stack trace is automatically included. * * @param message - The message to display as a note. Can be either: * - A string: Displayed directly with appropriate styling * - An Error object: The error message is displayed, and for level "error", the stack trace is automatically included * @param level - The note level: "info" (default), "warn" for warning styling, or "error" for error 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"); * * // Display error note (string) * note("Operation failed", "error"); * * // Display error with stack trace automatically (Error object) * try { * // some operation * } catch (error) { * // If error is an Error object and level is "error", stack trace is included automatically * note(error, "error"); * } */ const note = (message, level = "info") => { if (!canPrint(level)) { return; } const msg = prepareMessage(message, level); console.log(""); if (level === "warn") { console.warn(colorize(msg, level)); } else if (level === "error") { console.error(colorize(msg, level)); } else { console.log(msg); } }; //#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((0, yoctocolors.inverse)((0, yoctocolors.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) => { note(error, "error"); throw new SpinnerError(error.message, error); }; if (is_in_ci.default || !shouldPrint()) { try { return await options.task(); } catch (err) { return handleError(err); } } const spinner$1 = (0, yocto_spinner.default)({ 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((0, yoctocolors.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 console_table_printer.Table({ columns: columnKeys.map((key) => ({ name: key, title: (0, yoctocolors.whiteBright)(camelCaseToWords(key)), alignment: "left" })) }); table$1.addRows(data); table$1.printTable(); } //#endregion exports.CancelError = CancelError; exports.CommandError = CommandError; exports.SpinnerError = SpinnerError; exports.ascii = ascii; exports.cancel = cancel; exports.executeCommand = executeCommand; exports.intro = intro; exports.list = list; exports.maskTokens = maskTokens; exports.note = note; exports.outro = outro; exports.spinner = spinner; exports.table = table; //# sourceMappingURL=terminal.cjs.map