@settlemint/sdk-utils
Version:
Shared utilities and helper functions for SettleMint SDK modules
586 lines (572 loc) • 18 kB
JavaScript
//#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