@settlemint/sdk-utils 2.6.3-pre3cdc339 → 2.6.4-main400d9402

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -48,10 +48,18 @@ deepmerge_ts = __toESM(deepmerge_ts);
48
48
 
49
49
  //#region src/terminal/should-print.ts
50
50
  /**
51
- * Returns true if the terminal should print, false otherwise.
52
- * When CLAUDECODE, REPL_ID, or AGENT env vars are set, suppresses info/debug output
53
- * but warnings and errors will still be displayed.
54
- * @returns true if the terminal should print, false otherwise.
51
+ * Determines whether terminal output should be printed based on environment variables.
52
+ *
53
+ * **Environment Variable Precedence:**
54
+ * 1. `SETTLEMINT_DISABLE_TERMINAL="true"` - Completely disables all terminal output (highest priority)
55
+ * 2. `CLAUDECODE`, `REPL_ID`, or `AGENT` (any truthy value) - Enables quiet mode, suppressing info/debug/status messages
56
+ *
57
+ * **Quiet Mode Behavior:**
58
+ * When quiet mode is active (Claude Code environments), this function returns `false` to suppress
59
+ * informational output. However, warnings and errors are always displayed regardless of quiet mode,
60
+ * as they are handled separately in the `note()` function with level-based filtering.
61
+ *
62
+ * @returns `true` if terminal output should be printed, `false` if suppressed
55
63
  */
56
64
  function shouldPrint() {
57
65
  if (process.env.SETTLEMINT_DISABLE_TERMINAL === "true") {
@@ -263,13 +271,62 @@ const intro = (msg) => {
263
271
  //#endregion
264
272
  //#region src/terminal/note.ts
265
273
  /**
274
+ * Applies color to a message if not already colored.
275
+ * @param msg - The message to colorize
276
+ * @param level - The severity level determining the color
277
+ * @returns Colorized message (yellow for warnings, red for errors, unchanged for info)
278
+ */
279
+ function colorize(msg, level) {
280
+ if (msg.includes("\x1B[")) {
281
+ return msg;
282
+ }
283
+ if (level === "warn") {
284
+ return (0, yoctocolors.yellowBright)(msg);
285
+ }
286
+ if (level === "error") {
287
+ return (0, yoctocolors.redBright)(msg);
288
+ }
289
+ return msg;
290
+ }
291
+ /**
292
+ * Determines whether a message should be printed based on its level and quiet mode.
293
+ * @param level - The severity level of the message
294
+ * @returns true if the message should be printed, false otherwise
295
+ */
296
+ function canPrint(level) {
297
+ if (level !== "info") {
298
+ return true;
299
+ }
300
+ return shouldPrint();
301
+ }
302
+ /**
303
+ * Prepares a message for display by converting Error objects and masking tokens.
304
+ * @param value - The message string or Error object
305
+ * @param level - The severity level (stack traces are included for errors)
306
+ * @returns Masked message text, optionally with stack trace
307
+ */
308
+ function prepareMessage(value, level) {
309
+ let text;
310
+ if (value instanceof Error) {
311
+ text = value.message;
312
+ if (level === "error" && value.stack) {
313
+ text = `${text}\n\n${value.stack}`;
314
+ }
315
+ } else {
316
+ text = value;
317
+ }
318
+ return maskTokens(text);
319
+ }
320
+ /**
266
321
  * Displays a note message with optional warning or error level formatting.
267
322
  * Regular notes are displayed in normal text, warnings are shown in yellow, and errors in red.
268
323
  * Any sensitive tokens in the message are masked before display.
269
324
  * Warnings and errors are always displayed, even in quiet mode (when CLAUDECODE, REPL_ID, or AGENT env vars are set).
270
325
  * When an Error object is provided with level "error", the stack trace is automatically included.
271
326
  *
272
- * @param message - The message to display as a note, or an Error object
327
+ * @param message - The message to display as a note. Can be either:
328
+ * - A string: Displayed directly with appropriate styling
329
+ * - An Error object: The error message is displayed, and for level "error", the stack trace is automatically included
273
330
  * @param level - The note level: "info" (default), "warn" for warning styling, or "error" for error styling
274
331
  * @example
275
332
  * import { note } from "@settlemint/sdk-utils/terminal";
@@ -280,46 +337,30 @@ const intro = (msg) => {
280
337
  * // Display warning note
281
338
  * note("Low disk space remaining", "warn");
282
339
  *
283
- * // Display error note
340
+ * // Display error note (string)
284
341
  * note("Operation failed", "error");
285
342
  *
286
- * // Display error with stack trace automatically
343
+ * // Display error with stack trace automatically (Error object)
287
344
  * try {
288
345
  * // some operation
289
346
  * } catch (error) {
347
+ * // If error is an Error object and level is "error", stack trace is included automatically
290
348
  * note(error, "error");
291
349
  * }
292
350
  */
293
351
  const note = (message, level = "info") => {
294
- let messageText;
295
- let _error;
296
- if (message instanceof Error) {
297
- _error = message;
298
- messageText = message.message;
299
- if (level === "error" && message.stack) {
300
- messageText = `${messageText}\n\n${message.stack}`;
301
- }
302
- } else {
303
- messageText = message;
304
- }
305
- const maskedMessage = maskTokens(messageText);
306
- const _isQuietMode = process.env.CLAUDECODE || process.env.REPL_ID || process.env.AGENT;
307
- if (level === "warn" || level === "error") {
308
- console.log("");
309
- if (level === "warn") {
310
- const coloredMessage = maskedMessage.includes("\x1B[") ? maskedMessage : (0, yoctocolors.yellowBright)(maskedMessage);
311
- console.warn(coloredMessage);
312
- } else {
313
- const coloredMessage = maskedMessage.includes("\x1B[") ? maskedMessage : (0, yoctocolors.redBright)(maskedMessage);
314
- console.error(coloredMessage);
315
- }
316
- return;
317
- }
318
- if (!shouldPrint()) {
352
+ if (!canPrint(level)) {
319
353
  return;
320
354
  }
355
+ const msg = prepareMessage(message, level);
321
356
  console.log("");
322
- console.log(maskedMessage);
357
+ if (level === "warn") {
358
+ console.warn(colorize(msg, level));
359
+ } else if (level === "error") {
360
+ console.error(colorize(msg, level));
361
+ } else {
362
+ console.log(msg);
363
+ }
323
364
  };
324
365
 
325
366
  //#endregion
@@ -415,8 +456,7 @@ var SpinnerError = class extends Error {
415
456
  const spinner = async (options) => {
416
457
  const handleError = (error) => {
417
458
  note(error, "error");
418
- const errorMessage = maskTokens(error.message);
419
- throw new SpinnerError(errorMessage, error);
459
+ throw new SpinnerError(error.message, error);
420
460
  };
421
461
  if (is_in_ci.default || !shouldPrint()) {
422
462
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"environment.cjs","names":["code: number","output: string[]","stdoutOutput: string[]","stderrOutput: string[]","messageText: string","_error: Error | undefined","items","originalError: Error","isInCi","spinner","table","Table","ZodError","ApplicationAccessTokenSchema: ZodString","z","PersonalAccessTokenSchema: ZodString","AccessTokenSchema: ZodString","value","z","z","z","z"],"sources":["../src/terminal/should-print.ts","../src/terminal/ascii.ts","../src/logging/mask-tokens.ts","../src/terminal/cancel.ts","../src/terminal/execute-command.ts","../src/terminal/intro.ts","../src/terminal/note.ts","../src/terminal/list.ts","../src/terminal/outro.ts","../src/terminal/spinner.ts","../src/string.ts","../src/terminal/table.ts","../src/validation/validate.ts","../src/validation/access-token.schema.ts","../src/json.ts","../src/validation/unique-name.schema.ts","../src/validation/url.schema.ts","../src/validation/dot-env.schema.ts","../src/validation/id.schema.ts","../src/environment/load-env.ts","../src/filesystem/project-root.ts","../src/filesystem/exists.ts","../src/filesystem/mono-repo.ts","../src/environment/write-env.ts"],"sourcesContent":["/**\n * Returns true if the terminal should print, false otherwise.\n * When CLAUDECODE, REPL_ID, or AGENT env vars are set, suppresses info/debug output\n * but warnings and errors will still be displayed.\n * @returns true if the terminal should print, false otherwise.\n */\nexport function shouldPrint() {\n if (process.env.SETTLEMINT_DISABLE_TERMINAL === \"true\") {\n return false;\n }\n // In quiet mode (Claude Code), suppress info/debug/status messages\n // Warnings and errors will still be displayed via note() with appropriate levels\n if (process.env.CLAUDECODE || process.env.REPL_ID || process.env.AGENT) {\n return false;\n }\n return true;\n}\n","import { magentaBright } from \"yoctocolors\";\nimport { shouldPrint } from \"./should-print.js\";\n\n/**\n * Prints the SettleMint ASCII art logo to the console in magenta color.\n * Used for CLI branding and visual identification.\n *\n * @example\n * import { ascii } from \"@settlemint/sdk-utils/terminal\";\n *\n * // Prints the SettleMint logo\n * ascii();\n */\nexport const ascii = (): void => {\n if (!shouldPrint()) {\n return;\n }\n console.log(\n magentaBright(`\n _________ __ __ .__ _____ .__ __\n / _____/ _____/ |__/ |_| | ____ / \\\\ |__| _____/ |_\n \\\\_____ \\\\_/ __ \\\\ __\\\\ __\\\\ | _/ __ \\\\ / \\\\ / \\\\| |/ \\\\ __\\\\\n / \\\\ ___/| | | | | |_\\\\ ___// Y \\\\ | | \\\\ |\n/_________/\\\\_____>__| |__| |____/\\\\_____>____|____/__|___|__/__|\n`),\n );\n};\n","/**\n * Masks sensitive SettleMint tokens in output text by replacing them with asterisks.\n * Handles personal access tokens (PAT), application access tokens (AAT), and service account tokens (SAT).\n *\n * @param output - The text string that may contain sensitive tokens\n * @returns The text with any sensitive tokens masked with asterisks\n * @example\n * import { maskTokens } from \"@settlemint/sdk-utils/terminal\";\n *\n * // Masks a token in text\n * const masked = maskTokens(\"Token: sm_pat_****\"); // \"Token: ***\"\n */\nexport const maskTokens = (output: string): string => {\n return output.replace(/sm_(pat|aat|sat)_[0-9a-zA-Z]+/g, \"***\");\n};\n","import { maskTokens } from \"@/logging/mask-tokens.js\";\nimport { inverse, redBright } from \"yoctocolors\";\n\n/**\n * Error class used to indicate that the operation was cancelled.\n * This error is used to signal that the operation should be aborted.\n */\nexport class CancelError extends Error {}\n\n/**\n * Displays an error message in red inverse text and throws a CancelError.\n * Used to terminate execution with a visible error message.\n * Any sensitive tokens in the message are masked before display.\n *\n * @param msg - The error message to display\n * @returns never - Function does not return as it throws an error\n * @example\n * import { cancel } from \"@settlemint/sdk-utils/terminal\";\n *\n * // Exits process with error message\n * cancel(\"An error occurred\");\n */\nexport const cancel = (msg: string): never => {\n console.log(\"\");\n console.log(inverse(redBright(maskTokens(msg))));\n console.log(\"\");\n throw new CancelError(msg);\n};\n","import { type SpawnOptionsWithoutStdio, spawn } from \"node:child_process\";\nimport { maskTokens } from \"../logging/mask-tokens.js\";\n\n/**\n * Options for executing a command, extending SpawnOptionsWithoutStdio\n */\nexport interface ExecuteCommandOptions extends SpawnOptionsWithoutStdio {\n /** Whether to suppress output to stdout/stderr */\n silent?: boolean;\n}\n\n/**\n * Error class for command execution errors\n * @extends Error\n */\nexport class CommandError extends Error {\n /**\n * Constructs a new CommandError\n * @param message - The error message\n * @param code - The exit code of the command\n * @param output - The output of the command\n */\n constructor(\n message: string,\n public readonly code: number,\n public readonly output: string[],\n ) {\n super(message);\n }\n}\n\n/**\n * Checks if we're in quiet mode (Claude Code environment)\n */\nfunction isQuietMode(): boolean {\n return !!(process.env.CLAUDECODE || process.env.REPL_ID || process.env.AGENT);\n}\n\n/**\n * Executes a command with the given arguments in a child process.\n * Pipes stdin to the child process and captures stdout/stderr output.\n * Masks any sensitive tokens in the output before displaying or returning.\n * In quiet mode (when CLAUDECODE, REPL_ID, or AGENT env vars are set),\n * output is suppressed unless the command errors out.\n *\n * @param command - The command to execute\n * @param args - Array of arguments to pass to the command\n * @param options - Options for customizing command execution\n * @returns Array of output strings from stdout and stderr\n * @throws {CommandError} If the process fails to start or exits with non-zero code\n * @example\n * import { executeCommand } from \"@settlemint/sdk-utils/terminal\";\n *\n * // Execute git clone\n * await executeCommand(\"git\", [\"clone\", \"repo-url\"]);\n *\n * // Execute silently\n * await executeCommand(\"npm\", [\"install\"], { silent: true });\n */\nexport async function executeCommand(\n command: string,\n args: string[],\n options?: ExecuteCommandOptions,\n): Promise<string[]> {\n const { silent, ...spawnOptions } = options ?? {};\n const quietMode = isQuietMode();\n // In quiet mode, suppress output unless explicitly overridden with silent: false\n const shouldSuppressOutput = quietMode ? silent !== false : !!silent;\n\n const child = spawn(command, args, { ...spawnOptions, env: { ...process.env, ...options?.env } });\n process.stdin.pipe(child.stdin);\n const output: string[] = [];\n const stdoutOutput: string[] = [];\n const stderrOutput: string[] = [];\n\n return new Promise((resolve, reject) => {\n child.stdout.on(\"data\", (data: Buffer | string) => {\n const maskedData = maskTokens(data.toString());\n if (!shouldSuppressOutput) {\n process.stdout.write(maskedData);\n }\n output.push(maskedData);\n stdoutOutput.push(maskedData);\n });\n child.stderr.on(\"data\", (data: Buffer | string) => {\n const maskedData = maskTokens(data.toString());\n if (!shouldSuppressOutput) {\n process.stderr.write(maskedData);\n }\n output.push(maskedData);\n stderrOutput.push(maskedData);\n });\n\n const showErrorOutput = () => {\n // In quiet mode, show output on error\n if (quietMode && shouldSuppressOutput && output.length > 0) {\n // Write stdout to stdout and stderr to stderr\n if (stdoutOutput.length > 0) {\n process.stdout.write(stdoutOutput.join(\"\"));\n }\n if (stderrOutput.length > 0) {\n process.stderr.write(stderrOutput.join(\"\"));\n }\n }\n };\n\n child.on(\"error\", (err) => {\n process.stdin.unpipe(child.stdin);\n showErrorOutput();\n reject(new CommandError(err.message, \"code\" in err && typeof err.code === \"number\" ? err.code : 1, output));\n });\n child.on(\"close\", (code) => {\n process.stdin.unpipe(child.stdin);\n if (code === 0 || code === null || code === 143) {\n resolve(output);\n return;\n }\n // In quiet mode, show output on error\n showErrorOutput();\n reject(new CommandError(`Command \"${command}\" exited with code ${code}`, code, output));\n });\n });\n}\n","import { maskTokens } from \"@/logging/mask-tokens.js\";\nimport { magentaBright } from \"yoctocolors\";\nimport { shouldPrint } from \"./should-print.js\";\n\n/**\n * Displays an introductory message in magenta text with padding.\n * Any sensitive tokens in the message are masked before display.\n *\n * @param msg - The message to display as introduction\n * @example\n * import { intro } from \"@settlemint/sdk-utils/terminal\";\n *\n * // Display intro message\n * intro(\"Starting deployment...\");\n */\nexport const intro = (msg: string): void => {\n if (!shouldPrint()) {\n return;\n }\n console.log(\"\");\n console.log(magentaBright(maskTokens(msg)));\n console.log(\"\");\n};\n","import { maskTokens } from \"@/logging/mask-tokens.js\";\nimport { redBright, yellowBright } from \"yoctocolors\";\nimport { shouldPrint } from \"./should-print.js\";\n\n/**\n * Displays a note message with optional warning or error level formatting.\n * Regular notes are displayed in normal text, warnings are shown in yellow, and errors in red.\n * Any sensitive tokens in the message are masked before display.\n * Warnings and errors are always displayed, even in quiet mode (when CLAUDECODE, REPL_ID, or AGENT env vars are set).\n * When an Error object is provided with level \"error\", the stack trace is automatically included.\n *\n * @param message - The message to display as a note, or an Error object\n * @param level - The note level: \"info\" (default), \"warn\" for warning styling, or \"error\" for error styling\n * @example\n * import { note } from \"@settlemint/sdk-utils/terminal\";\n *\n * // Display info note\n * note(\"Operation completed successfully\");\n *\n * // Display warning note\n * note(\"Low disk space remaining\", \"warn\");\n *\n * // Display error note\n * note(\"Operation failed\", \"error\");\n *\n * // Display error with stack trace automatically\n * try {\n * // some operation\n * } catch (error) {\n * note(error, \"error\");\n * }\n */\nexport const note = (message: string | Error, level: \"info\" | \"warn\" | \"error\" = \"info\"): void => {\n let messageText: string;\n let _error: Error | undefined;\n\n if (message instanceof Error) {\n _error = message;\n messageText = message.message;\n // For errors, automatically include stack trace\n if (level === \"error\" && message.stack) {\n messageText = `${messageText}\\n\\n${message.stack}`;\n }\n } else {\n messageText = message;\n }\n\n const maskedMessage = maskTokens(messageText);\n const _isQuietMode = process.env.CLAUDECODE || process.env.REPL_ID || process.env.AGENT;\n\n // Always print warnings and errors, even in quiet mode\n if (level === \"warn\" || level === \"error\") {\n console.log(\"\");\n if (level === \"warn\") {\n // Apply yellow color if not already colored (check if message contains ANSI codes)\n const coloredMessage = maskedMessage.includes(\"\\u001b[\") ? maskedMessage : yellowBright(maskedMessage);\n console.warn(coloredMessage);\n } else {\n // Apply red color if not already colored (check if message contains ANSI codes)\n const coloredMessage = maskedMessage.includes(\"\\u001b[\") ? maskedMessage : redBright(maskedMessage);\n console.error(coloredMessage);\n }\n return;\n }\n\n // For info messages, check if we should print\n if (!shouldPrint()) {\n return;\n }\n\n console.log(\"\");\n console.log(maskedMessage);\n};\n","import { note } from \"./note.js\";\n\n/**\n * Displays a list of items in a formatted manner, supporting nested items.\n *\n * @param title - The title of the list\n * @param items - The items to display, can be strings or arrays for nested items\n * @returns The formatted list\n * @example\n * import { list } from \"@settlemint/sdk-utils/terminal\";\n *\n * // Simple list\n * list(\"Use cases\", [\"use case 1\", \"use case 2\", \"use case 3\"]);\n *\n * // Nested list\n * list(\"Providers\", [\n * \"AWS\",\n * [\"us-east-1\", \"eu-west-1\"],\n * \"Azure\",\n * [\"eastus\", \"westeurope\"]\n * ]);\n */\nexport function list(title: string, items: Array<string | string[]>) {\n const formatItems = (items: Array<string | string[]>): string => {\n return items\n .map((item) => {\n if (Array.isArray(item)) {\n return item.map((subItem) => ` • ${subItem}`).join(\"\\n\");\n }\n return ` • ${item}`;\n })\n .join(\"\\n\");\n };\n\n return note(`${title}:\\n\\n${formatItems(items)}`);\n}\n","import { maskTokens } from \"@/logging/mask-tokens.js\";\nimport { shouldPrint } from \"@/terminal/should-print.js\";\nimport { greenBright, inverse } from \"yoctocolors\";\n\n/**\n * Displays a closing message in green inverted text with padding.\n * Any sensitive tokens in the message are masked before display.\n *\n * @param msg - The message to display as conclusion\n * @example\n * import { outro } from \"@settlemint/sdk-utils/terminal\";\n *\n * // Display outro message\n * outro(\"Deployment completed successfully!\");\n */\nexport const outro = (msg: string): void => {\n if (!shouldPrint()) {\n return;\n }\n console.log(\"\");\n console.log(inverse(greenBright(maskTokens(msg))));\n console.log(\"\");\n};\n","import isInCi from \"is-in-ci\";\nimport yoctoSpinner, { type Spinner } from \"yocto-spinner\";\nimport { redBright } from \"yoctocolors\";\nimport { maskTokens } from \"../logging/mask-tokens.js\";\nimport { note } from \"./note.js\";\nimport { shouldPrint } from \"./should-print.js\";\n\n/**\n * Error class used to indicate that the spinner operation failed.\n * This error is used to signal that the operation should be aborted.\n */\nexport class SpinnerError extends Error {\n constructor(\n message: string,\n public readonly originalError: Error,\n ) {\n super(message);\n this.name = \"SpinnerError\";\n }\n}\n\n/**\n * Options for configuring the spinner behavior\n */\nexport interface SpinnerOptions<R> {\n /** Message to display when spinner starts */\n startMessage: string;\n /** Async task to execute while spinner is active */\n task: (spinner?: Spinner) => Promise<R>;\n /** Message to display when spinner completes successfully */\n stopMessage: string;\n}\n\n/**\n * Displays a loading spinner while executing an async task.\n * Shows progress with start/stop messages and handles errors.\n * Spinner is disabled in CI environments.\n *\n * @param options - Configuration options for the spinner\n * @returns The result from the executed task\n * @throws Will exit process with code 1 if task fails\n * @example\n * import { spinner } from \"@settlemint/sdk-utils/terminal\";\n *\n * // Show spinner during async task\n * const result = await spinner({\n * startMessage: \"Deploying...\",\n * task: async () => {\n * // Async work here\n * return \"success\";\n * },\n * stopMessage: \"Deployed successfully!\"\n * });\n */\nexport const spinner = async <R>(options: SpinnerOptions<R>): Promise<R> => {\n const handleError = (error: Error) => {\n note(error, \"error\");\n const errorMessage = maskTokens(error.message);\n throw new SpinnerError(errorMessage, error);\n };\n if (isInCi || !shouldPrint()) {\n try {\n return await options.task();\n } catch (err) {\n return handleError(err as Error);\n }\n }\n const spinner = yoctoSpinner({ stream: process.stdout }).start(options.startMessage);\n try {\n const result = await options.task(spinner);\n spinner.success(options.stopMessage);\n // Ensure spinner success message renders before proceeding to avoid\n // terminal output overlap issues with subsequent messages\n await new Promise((resolve) => process.nextTick(resolve));\n return result;\n } catch (err) {\n spinner.error(redBright(`${options.startMessage} --> Error!`));\n return handleError(err as Error);\n }\n};\n","/**\n * Capitalizes the first letter of a string.\n *\n * @param val - The string to capitalize\n * @returns The input string with its first letter capitalized\n *\n * @example\n * import { capitalizeFirstLetter } from \"@settlemint/sdk-utils\";\n *\n * const capitalized = capitalizeFirstLetter(\"hello\");\n * // Returns: \"Hello\"\n */\nexport function capitalizeFirstLetter(val: string) {\n return String(val).charAt(0).toUpperCase() + String(val).slice(1);\n}\n\n/**\n * Converts a camelCase string to a human-readable string.\n *\n * @param s - The camelCase string to convert\n * @returns The human-readable string\n *\n * @example\n * import { camelCaseToWords } from \"@settlemint/sdk-utils\";\n *\n * const words = camelCaseToWords(\"camelCaseString\");\n * // Returns: \"Camel Case String\"\n */\nexport function camelCaseToWords(s: string) {\n const result = s.replace(/([a-z])([A-Z])/g, \"$1 $2\");\n const withSpaces = result.replace(/([A-Z])([a-z])/g, \" $1$2\");\n const capitalized = capitalizeFirstLetter(withSpaces);\n return capitalized.replace(/\\s+/g, \" \").trim();\n}\n\n/**\n * Replaces underscores and hyphens with spaces.\n *\n * @param s - The string to replace underscores and hyphens with spaces\n * @returns The input string with underscores and hyphens replaced with spaces\n *\n * @example\n * import { replaceUnderscoresAndHyphensWithSpaces } from \"@settlemint/sdk-utils\";\n *\n * const result = replaceUnderscoresAndHyphensWithSpaces(\"Already_Spaced-Second\");\n * // Returns: \"Already Spaced Second\"\n */\nexport function replaceUnderscoresAndHyphensWithSpaces(s: string) {\n return s.replace(/[-_]/g, \" \");\n}\n\n/**\n * Truncates a string to a maximum length and appends \"...\" if it is longer.\n *\n * @param value - The string to truncate\n * @param maxLength - The maximum length of the string\n * @returns The truncated string or the original string if it is shorter than the maximum length\n *\n * @example\n * import { truncate } from \"@settlemint/sdk-utils\";\n *\n * const truncated = truncate(\"Hello, world!\", 10);\n * // Returns: \"Hello, wor...\"\n */\nexport function truncate(value: string, maxLength: number) {\n if (value.length <= maxLength) {\n return value;\n }\n return `${value.slice(0, maxLength)}...`;\n}\n","import { Table } from \"console-table-printer\";\nimport { whiteBright } from \"yoctocolors\";\nimport { camelCaseToWords } from \"@/string.js\";\nimport { note } from \"./note.js\";\nimport { shouldPrint } from \"./should-print.js\";\n/**\n * Displays data in a formatted table in the terminal.\n *\n * @param title - Title to display above the table\n * @param data - Array of objects to display in table format\n * @example\n * import { table } from \"@settlemint/sdk-utils/terminal\";\n *\n * const data = [\n * { name: \"Item 1\", value: 100 },\n * { name: \"Item 2\", value: 200 }\n * ];\n *\n * table(\"My Table\", data);\n */\nexport function table(title: string, data: unknown[]): void {\n if (!shouldPrint()) {\n return;\n }\n\n note(title);\n\n if (!data || data.length === 0) {\n note(\"No data to display\");\n return;\n }\n\n const columnKeys = Object.keys(data[0] as Record<string, unknown>);\n const table = new Table({\n columns: columnKeys.map((key) => ({\n name: key,\n title: whiteBright(camelCaseToWords(key)),\n alignment: \"left\",\n })),\n });\n // biome-ignore lint/suspicious/noExplicitAny: Data structure varies based on table content\n table.addRows(data as Array<Record<string, any>>);\n table.printTable();\n}\n","import { ZodError, type ZodType } from \"zod\";\n\n/**\n * Validates a value against a given Zod schema.\n *\n * @param schema - The Zod schema to validate against.\n * @param value - The value to validate.\n * @returns The validated and parsed value.\n * @throws Will throw an error if validation fails, with formatted error messages.\n *\n * @example\n * import { validate } from \"@settlemint/sdk-utils/validation\";\n *\n * const validatedId = validate(IdSchema, \"550e8400-e29b-41d4-a716-446655440000\");\n */\nexport function validate<T extends ZodType>(schema: T, value: unknown): T[\"_output\"] {\n try {\n return schema.parse(value);\n } catch (error) {\n if (error instanceof ZodError) {\n const formattedErrors = error.issues.map((err) => `- ${err.path.join(\".\")}: ${err.message}`).join(\"\\n\");\n throw new Error(`Validation error${error.issues.length > 1 ? \"s\" : \"\"}:\\n${formattedErrors}`);\n }\n throw error; // Re-throw if it's not a ZodError\n }\n}\n","import { type ZodString, z } from \"zod\";\n\n/**\n * Schema for validating application access tokens.\n * Application access tokens start with 'sm_aat_' prefix.\n */\nexport const ApplicationAccessTokenSchema: ZodString = z.string().regex(/^sm_aat_.+$/);\nexport type ApplicationAccessToken = z.infer<typeof ApplicationAccessTokenSchema>;\n\n/**\n * Schema for validating personal access tokens.\n * Personal access tokens start with 'sm_pat_' prefix.\n */\nexport const PersonalAccessTokenSchema: ZodString = z.string().regex(/^sm_pat_.+$/);\nexport type PersonalAccessToken = z.infer<typeof PersonalAccessTokenSchema>;\n\n/**\n * Schema for validating both application and personal access tokens.\n * Accepts tokens starting with either 'sm_pat_' or 'sm_aat_' prefix.\n */\nexport const AccessTokenSchema: ZodString = z.string().regex(/^(sm_pat_.+|sm_aat_.+)$/);\nexport type AccessToken = z.infer<typeof AccessTokenSchema>;\n","/**\n * Attempts to parse a JSON string into a typed value, returning a default value if parsing fails.\n *\n * @param value - The JSON string to parse\n * @param defaultValue - The value to return if parsing fails or results in null/undefined\n * @returns The parsed JSON value as type T, or the default value if parsing fails\n *\n * @example\n * import { tryParseJson } from \"@settlemint/sdk-utils\";\n *\n * const config = tryParseJson<{ port: number }>(\n * '{\"port\": 3000}',\n * { port: 8080 }\n * );\n * // Returns: { port: 3000 }\n *\n * const invalid = tryParseJson<string[]>(\n * 'invalid json',\n * []\n * );\n * // Returns: []\n */\nexport function tryParseJson<T>(value: string, defaultValue: T | null = null): T | null {\n try {\n const parsed = JSON.parse(value) as T;\n if (parsed === undefined || parsed === null) {\n return defaultValue;\n }\n return parsed;\n } catch (_err) {\n // Invalid json\n return defaultValue;\n }\n}\n\n/**\n * Extracts a JSON object from a string.\n *\n * @param value - The string to extract the JSON object from\n * @returns The parsed JSON object, or null if no JSON object is found\n * @throws {Error} If the input string is too long (longer than 5000 characters)\n * @example\n * import { extractJsonObject } from \"@settlemint/sdk-utils\";\n *\n * const json = extractJsonObject<{ port: number }>(\n * 'port info: {\"port\": 3000}',\n * );\n * // Returns: { port: 3000 }\n */\nexport function extractJsonObject<T>(value: string): T | null {\n if (value.length > 5000) {\n throw new Error(\"Input too long\");\n }\n const result = /\\{([\\s\\S]*)\\}/.exec(value);\n if (!result) {\n return null;\n }\n return tryParseJson<T>(result[0]);\n}\n\n/**\n * Converts a value to a JSON stringifiable format.\n *\n * @param value - The value to convert\n * @returns The JSON stringifiable value\n *\n * @example\n * import { makeJsonStringifiable } from \"@settlemint/sdk-utils\";\n *\n * const json = makeJsonStringifiable<{ amount: bigint }>({ amount: BigInt(1000) });\n * // Returns: '{\"amount\":\"1000\"}'\n */\nexport function makeJsonStringifiable<T>(value: unknown): T {\n if (value === undefined || value === null) {\n return value as T;\n }\n return tryParseJson<T>(\n JSON.stringify(\n value,\n (_, value) => (typeof value === \"bigint\" ? value.toString() : value), // return everything else unchanged\n ),\n ) as T;\n}\n","import { z } from \"zod\";\n\n/**\n * Schema for validating unique names used across the SettleMint platform.\n * Only accepts lowercase alphanumeric characters and hyphens.\n * Used for workspace names, application names, service names etc.\n *\n * @example\n * import { UniqueNameSchema } from \"@settlemint/sdk-utils/validation\";\n *\n * // Validate a workspace name\n * const isValidName = UniqueNameSchema.safeParse(\"my-workspace-123\").success;\n * // true\n *\n * // Invalid names will fail validation\n * const isInvalidName = UniqueNameSchema.safeParse(\"My Workspace!\").success;\n * // false\n */\nexport const UniqueNameSchema = z.string().regex(/^[a-z0-9-]+$/);\n\n/**\n * Type definition for unique names, inferred from UniqueNameSchema.\n */\nexport type UniqueName = z.infer<typeof UniqueNameSchema>;\n","import { z } from \"zod\";\n\n/**\n * Schema for validating URLs.\n *\n * @example\n * import { UrlSchema } from \"@settlemint/sdk-utils/validation\";\n *\n * // Validate a URL\n * const isValidUrl = UrlSchema.safeParse(\"https://console.settlemint.com\").success;\n * // true\n *\n * // Invalid URLs will fail validation\n * const isInvalidUrl = UrlSchema.safeParse(\"not-a-url\").success;\n * // false\n */\nexport const UrlSchema = z.string().url();\nexport type Url = z.infer<typeof UrlSchema>;\n\n/**\n * Schema for validating URL paths.\n *\n * @example\n * import { UrlPathSchema } from \"@settlemint/sdk-utils/validation\";\n *\n * // Validate a URL path\n * const isValidPath = UrlPathSchema.safeParse(\"/api/v1/users\").success;\n * // true\n *\n * // Invalid paths will fail validation\n * const isInvalidPath = UrlPathSchema.safeParse(\"not-a-path\").success;\n * // false\n */\nexport const UrlPathSchema = z.string().regex(/^\\/(?:[a-zA-Z0-9-_]+(?:\\/[a-zA-Z0-9-_]+)*\\/?)?$/, {\n message: \"Invalid URL path format. Must start with '/' and can contain letters, numbers, hyphens, and underscores.\",\n});\n\nexport type UrlPath = z.infer<typeof UrlPathSchema>;\n\n/**\n * Schema that accepts either a full URL or a URL path.\n *\n * @example\n * import { UrlOrPathSchema } from \"@settlemint/sdk-utils/validation\";\n *\n * // Validate a URL\n * const isValidUrl = UrlOrPathSchema.safeParse(\"https://console.settlemint.com\").success;\n * // true\n *\n * // Validate a path\n * const isValidPath = UrlOrPathSchema.safeParse(\"/api/v1/users\").success;\n * // true\n */\nexport const UrlOrPathSchema = z.union([UrlSchema, UrlPathSchema]);\nexport type UrlOrPath = z.infer<typeof UrlOrPathSchema>;\n","import { z } from \"zod\";\nimport { tryParseJson } from \"@/json.js\";\nimport { ApplicationAccessTokenSchema, PersonalAccessTokenSchema } from \"./access-token.schema.js\";\nimport { UniqueNameSchema } from \"./unique-name.schema.js\";\nimport { UrlSchema } from \"./url.schema.js\";\n\n/**\n * Use this value to indicate that the resources are not part of the SettleMint platform.\n */\nexport const STANDALONE_INSTANCE = \"standalone\";\n/**\n * Use this value to indicate that the resources are not part of the SettleMint platform.\n */\nexport const LOCAL_INSTANCE = \"local\";\n\n/**\n * Schema for validating environment variables used by the SettleMint SDK.\n * Defines validation rules and types for configuration values like URLs,\n * access tokens, workspace names, and service endpoints.\n */\nexport const DotEnvSchema = z.object({\n /** Base URL of the SettleMint platform instance, set to standalone if your resources are not part of the SettleMint platform */\n SETTLEMINT_INSTANCE: z\n .union([UrlSchema, z.literal(STANDALONE_INSTANCE), z.literal(LOCAL_INSTANCE)])\n .default(\"https://console.settlemint.com\"),\n /** Application access token for authenticating with SettleMint services */\n SETTLEMINT_ACCESS_TOKEN: ApplicationAccessTokenSchema.optional(),\n /** @internal */\n SETTLEMINT_PERSONAL_ACCESS_TOKEN: PersonalAccessTokenSchema.optional(),\n /** Unique name of the workspace */\n SETTLEMINT_WORKSPACE: UniqueNameSchema.optional(),\n /** Unique name of the application */\n SETTLEMINT_APPLICATION: UniqueNameSchema.optional(),\n /** Unique name of the blockchain network */\n SETTLEMINT_BLOCKCHAIN_NETWORK: UniqueNameSchema.optional(),\n /** Chain ID of the blockchain network */\n SETTLEMINT_BLOCKCHAIN_NETWORK_CHAIN_ID: z.string().optional(),\n /** Unique name of the blockchain node (should have a private key for signing transactions) */\n SETTLEMINT_BLOCKCHAIN_NODE: UniqueNameSchema.optional(),\n /** JSON RPC endpoint for the blockchain node */\n SETTLEMINT_BLOCKCHAIN_NODE_JSON_RPC_ENDPOINT: UrlSchema.optional(),\n /** Unique name of the blockchain node or load balancer */\n SETTLEMINT_BLOCKCHAIN_NODE_OR_LOAD_BALANCER: UniqueNameSchema.optional(),\n /** JSON RPC endpoint for the blockchain node or load balancer */\n SETTLEMINT_BLOCKCHAIN_NODE_OR_LOAD_BALANCER_JSON_RPC_ENDPOINT: UrlSchema.optional(),\n /** Unique name of the Hasura instance */\n SETTLEMINT_HASURA: UniqueNameSchema.optional(),\n /** Endpoint URL for the Hasura GraphQL API */\n SETTLEMINT_HASURA_ENDPOINT: UrlSchema.optional(),\n /** Admin secret for authenticating with Hasura */\n SETTLEMINT_HASURA_ADMIN_SECRET: z.string().optional(),\n /** Database connection URL for Hasura */\n SETTLEMINT_HASURA_DATABASE_URL: z.string().optional(),\n /** Unique name of The Graph instance */\n SETTLEMINT_THEGRAPH: UniqueNameSchema.optional(),\n /** Array of endpoint URLs for The Graph subgraphs */\n SETTLEMINT_THEGRAPH_SUBGRAPHS_ENDPOINTS: z.preprocess(\n (value) => tryParseJson(value as string, []),\n z.array(UrlSchema).optional(),\n ),\n /** Default The Graph subgraph to use */\n SETTLEMINT_THEGRAPH_DEFAULT_SUBGRAPH: z.string().optional(),\n /** Unique name of the Smart Contract Portal instance */\n SETTLEMINT_PORTAL: UniqueNameSchema.optional(),\n /** GraphQL endpoint URL for the Portal */\n SETTLEMINT_PORTAL_GRAPHQL_ENDPOINT: UrlSchema.optional(),\n /** REST endpoint URL for the Portal */\n SETTLEMINT_PORTAL_REST_ENDPOINT: UrlSchema.optional(),\n /** WebSocket endpoint URL for the Portal */\n SETTLEMINT_PORTAL_WS_ENDPOINT: UrlSchema.optional(),\n /** Unique name of the HD private key */\n SETTLEMINT_HD_PRIVATE_KEY: UniqueNameSchema.optional(),\n /** Address of the HD private key forwarder */\n SETTLEMINT_HD_PRIVATE_KEY_FORWARDER_ADDRESS: z.string().optional(),\n /** Unique name of the accessible private key */\n SETTLEMINT_ACCESSIBLE_PRIVATE_KEY: UniqueNameSchema.optional(),\n /** Unique name of the MinIO instance */\n SETTLEMINT_MINIO: UniqueNameSchema.optional(),\n /** Endpoint URL for MinIO */\n SETTLEMINT_MINIO_ENDPOINT: UrlSchema.optional(),\n /** Access key for MinIO authentication */\n SETTLEMINT_MINIO_ACCESS_KEY: z.string().optional(),\n /** Secret key for MinIO authentication */\n SETTLEMINT_MINIO_SECRET_KEY: z.string().optional(),\n /** Unique name of the IPFS instance */\n SETTLEMINT_IPFS: UniqueNameSchema.optional(),\n /** API endpoint URL for IPFS */\n SETTLEMINT_IPFS_API_ENDPOINT: UrlSchema.optional(),\n /** Pinning service endpoint URL for IPFS */\n SETTLEMINT_IPFS_PINNING_ENDPOINT: UrlSchema.optional(),\n /** Gateway endpoint URL for IPFS */\n SETTLEMINT_IPFS_GATEWAY_ENDPOINT: UrlSchema.optional(),\n /** Unique name of the custom deployment */\n SETTLEMINT_CUSTOM_DEPLOYMENT: UniqueNameSchema.optional(),\n /** Endpoint URL for the custom deployment */\n SETTLEMINT_CUSTOM_DEPLOYMENT_ENDPOINT: UrlSchema.optional(),\n /** Unique name of the Blockscout instance */\n SETTLEMINT_BLOCKSCOUT: UniqueNameSchema.optional(),\n /** GraphQL endpoint URL for Blockscout */\n SETTLEMINT_BLOCKSCOUT_GRAPHQL_ENDPOINT: UrlSchema.optional(),\n /** UI endpoint URL for Blockscout */\n SETTLEMINT_BLOCKSCOUT_UI_ENDPOINT: UrlSchema.optional(),\n /** Name of the new project being created */\n SETTLEMINT_NEW_PROJECT_NAME: z.string().optional(),\n /** The log level to use */\n SETTLEMINT_LOG_LEVEL: z.enum([\"debug\", \"info\", \"warn\", \"error\", \"none\"]).default(\"warn\"),\n});\n\n/**\n * Type definition for the environment variables schema.\n */\nexport type DotEnv = z.infer<typeof DotEnvSchema>;\n\n/**\n * Partial version of the environment variables schema where all fields are optional.\n * Useful for validating incomplete configurations during development or build time.\n */\nexport const DotEnvSchemaPartial = DotEnvSchema.partial();\n\n/**\n * Type definition for the partial environment variables schema.\n */\nexport type DotEnvPartial = z.infer<typeof DotEnvSchemaPartial>;\n","import { z } from \"zod\";\n\n/**\n * Schema for validating database IDs. Accepts both PostgreSQL UUIDs and MongoDB ObjectIDs.\n * PostgreSQL UUIDs are 32 hexadecimal characters with hyphens (e.g. 123e4567-e89b-12d3-a456-426614174000).\n * MongoDB ObjectIDs are 24 hexadecimal characters (e.g. 507f1f77bcf86cd799439011).\n *\n * @example\n * import { IdSchema } from \"@settlemint/sdk-utils/validation\";\n *\n * // Validate PostgreSQL UUID\n * const isValidUuid = IdSchema.safeParse(\"123e4567-e89b-12d3-a456-426614174000\").success;\n *\n * // Validate MongoDB ObjectID\n * const isValidObjectId = IdSchema.safeParse(\"507f1f77bcf86cd799439011\").success;\n */\nexport const IdSchema = z.union([\n z\n .string()\n .uuid(), // PostgreSQL UUID\n z\n .string()\n .regex(/^[0-9a-fA-F]{24}$/), // MongoDB ObjectID\n]);\n\n/**\n * Type definition for database IDs, inferred from IdSchema.\n * Can be either a PostgreSQL UUID string or MongoDB ObjectID string.\n */\nexport type Id = z.infer<typeof IdSchema>;\n","import { join } from \"node:path\";\nimport { cancel } from \"@/terminal.js\";\nimport { type DotEnv, type DotEnvPartial, DotEnvSchema, DotEnvSchemaPartial, validate } from \"@/validation.js\";\nimport { config } from \"@dotenvx/dotenvx\";\n\n/**\n * Loads environment variables from .env files.\n * To enable encryption with dotenvx (https://www.dotenvx.com/docs) run `bunx dotenvx encrypt`\n *\n * @param validateEnv - Whether to validate the environment variables against the schema\n * @param prod - Whether to load production environment variables\n * @param path - Optional path to the directory containing .env files. Defaults to process.cwd()\n * @returns A promise that resolves to the validated environment variables\n * @throws Will throw an error if validation fails and validateEnv is true\n * @example\n * import { loadEnv } from '@settlemint/sdk-utils/environment';\n *\n * // Load and validate environment variables\n * const env = await loadEnv(true, false);\n * console.log(env.SETTLEMINT_INSTANCE);\n *\n * // Load without validation\n * const rawEnv = await loadEnv(false, false);\n */\nexport async function loadEnv<T extends boolean = true>(\n validateEnv: T,\n prod: boolean,\n path: string = process.cwd(),\n): Promise<T extends true ? DotEnv : DotEnvPartial> {\n return loadEnvironmentEnv(validateEnv, !!prod, path);\n}\n\nasync function loadEnvironmentEnv<T extends boolean = true>(\n validateEnv: T,\n prod: boolean,\n path: string = process.cwd(),\n): Promise<T extends true ? DotEnv : DotEnvPartial> {\n if (prod) {\n process.env.NODE_ENV = \"production\";\n }\n let { parsed } = config({\n convention: \"nextjs\",\n logLevel: \"error\",\n overload: true,\n quiet: true,\n path: [join(path, \".env\"), join(path, \".env.local\")],\n });\n\n if (!parsed) {\n parsed = {};\n }\n const defaultEnv = Object.fromEntries(\n Object.entries(process.env).filter(([_, value]) => typeof value === \"string\" && value !== \"\"),\n ) as Record<string, string>;\n try {\n return validate(validateEnv ? DotEnvSchema : DotEnvSchemaPartial, {\n ...parsed,\n ...defaultEnv,\n }) as T extends true ? DotEnv : DotEnvPartial;\n } catch (error) {\n cancel((error as Error).message);\n return {} as T extends true ? DotEnv : DotEnvPartial;\n }\n}\n","import { dirname } from \"node:path\";\nimport { findUp } from \"find-up\";\n\n/**\n * Finds the root directory of the current project by locating the nearest package.json file\n *\n * @param fallbackToCwd - If true, will return the current working directory if no package.json is found\n * @param cwd - The directory to start searching for the package.json file from (defaults to process.cwd())\n * @returns Promise that resolves to the absolute path of the project root directory\n * @throws Will throw an error if no package.json is found in the directory tree\n * @example\n * import { projectRoot } from \"@settlemint/sdk-utils/filesystem\";\n *\n * // Get project root path\n * const rootDir = await projectRoot();\n * console.log(`Project root is at: ${rootDir}`);\n */\nexport async function projectRoot(fallbackToCwd = false, cwd?: string): Promise<string> {\n const packageJsonPath = await findUp(\"package.json\", { cwd });\n if (!packageJsonPath) {\n if (fallbackToCwd) {\n return process.cwd();\n }\n throw new Error(\"Unable to find project root (no package.json found)\");\n }\n return dirname(packageJsonPath);\n}\n","import type { PathLike } from \"node:fs\";\nimport { stat } from \"node:fs/promises\";\n\n/**\n * Checks if a file or directory exists at the given path\n *\n * @param path - The file system path to check for existence\n * @returns Promise that resolves to true if the path exists, false otherwise\n * @example\n * import { exists } from \"@settlemint/sdk-utils/filesystem\";\n *\n * // Check if file exists before reading\n * if (await exists('/path/to/file.txt')) {\n * // File exists, safe to read\n * }\n */\nexport async function exists(path: PathLike): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n}\n","import { readFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { findUp } from \"find-up\";\nimport { glob } from \"glob\";\nimport { exists } from \"@/filesystem.js\";\nimport { tryParseJson } from \"@/json.js\";\n\n/**\n * Finds the root directory of a monorepo\n *\n * @param startDir - The directory to start searching from\n * @returns The root directory of the monorepo or null if not found\n * @example\n * import { findMonoRepoRoot } from \"@settlemint/sdk-utils/filesystem\";\n *\n * const root = await findMonoRepoRoot(\"/path/to/your/project\");\n * console.log(root); // Output: /path/to/your/project/packages/core\n */\nexport async function findMonoRepoRoot(startDir: string): Promise<string | null> {\n const lockFilePath = await findUp([\"package-lock.json\", \"yarn.lock\", \"pnpm-lock.yaml\", \"bun.lockb\", \"bun.lock\"], {\n cwd: startDir,\n });\n if (lockFilePath) {\n const packageJsonPath = join(dirname(lockFilePath), \"package.json\");\n const hasWorkSpaces = await packageJsonHasWorkspaces(packageJsonPath);\n return hasWorkSpaces ? dirname(lockFilePath) : null;\n }\n\n let currentDir = startDir;\n\n while (currentDir !== \"/\") {\n const packageJsonPath = join(currentDir, \"package.json\");\n\n if (await packageJsonHasWorkspaces(packageJsonPath)) {\n return currentDir;\n }\n\n const parentDir = dirname(currentDir);\n if (parentDir === currentDir) {\n break; // We've reached the root\n }\n currentDir = parentDir;\n }\n\n return null;\n}\n\n/**\n * Finds all packages in a monorepo\n *\n * @param projectDir - The directory to start searching from\n * @returns An array of package directories\n * @example\n * import { findMonoRepoPackages } from \"@settlemint/sdk-utils/filesystem\";\n *\n * const packages = await findMonoRepoPackages(\"/path/to/your/project\");\n * console.log(packages); // Output: [\"/path/to/your/project/packages/core\", \"/path/to/your/project/packages/ui\"]\n */\nexport async function findMonoRepoPackages(projectDir: string): Promise<string[]> {\n try {\n const monoRepoRoot = await findMonoRepoRoot(projectDir);\n if (!monoRepoRoot) {\n return [projectDir];\n }\n\n const packageJsonPath = join(monoRepoRoot, \"package.json\");\n const packageJson = tryParseJson<{ workspaces: string[] }>(await readFile(packageJsonPath, \"utf-8\"));\n const workspaces = packageJson?.workspaces ?? [];\n\n const packagePaths = await Promise.all(\n workspaces.map(async (workspace: string) => {\n const matches = await glob(join(monoRepoRoot, workspace, \"package.json\"));\n return matches.map((match) => join(match, \"..\"));\n }),\n );\n\n const allPaths = packagePaths.flat();\n // If no packages found in workspaces, treat as non-monorepo\n return allPaths.length === 0 ? [projectDir] : [monoRepoRoot, ...allPaths];\n } catch (_error) {\n // If any error occurs, treat as non-monorepo\n return [projectDir];\n }\n}\n\nasync function packageJsonHasWorkspaces(packageJsonPath: string): Promise<boolean> {\n if (await exists(packageJsonPath)) {\n const packageJson = tryParseJson<{ workspaces: string[] }>(await readFile(packageJsonPath, \"utf-8\"));\n if (packageJson?.workspaces && Array.isArray(packageJson?.workspaces) && packageJson?.workspaces.length > 0) {\n return true;\n }\n }\n return false;\n}\n","import { writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { exists, projectRoot } from \"@/filesystem.js\";\nimport type { DotEnv } from \"@/validation.js\";\nimport { DotEnvSchema } from \"@/validation/dot-env.schema.js\";\nimport { config } from \"@dotenvx/dotenvx\";\nimport { deepmerge } from \"deepmerge-ts\";\nimport { findMonoRepoPackages } from \"../filesystem/mono-repo.js\";\n\n/**\n * Writes environment variables to .env files across a project or monorepo\n *\n * @param options - The options for writing the environment variables\n * @param options.prod - Whether to write production environment variables\n * @param options.env - The environment variables to write\n * @param options.secrets - Whether to write to .env.local files for secrets\n * @param options.cwd - The directory to start searching for the package.json file from (defaults to process.cwd())\n * @returns Promise that resolves when writing is complete\n * @throws Will throw an error if writing fails\n * @example\n * import { writeEnv } from '@settlemint/sdk-utils/environment';\n *\n * // Write development environment variables\n * await writeEnv({\n * prod: false,\n * env: {\n * SETTLEMINT_INSTANCE: 'https://dev.example.com'\n * },\n * secrets: false\n * });\n *\n * // Write production secrets\n * await writeEnv({\n * prod: true,\n * env: {\n * SETTLEMINT_ACCESS_TOKEN: 'secret-token'\n * },\n * secrets: true\n * });\n */\nexport async function writeEnv({\n prod,\n env,\n secrets,\n cwd,\n}: {\n prod: boolean;\n env: Partial<DotEnv>;\n secrets: boolean;\n cwd?: string;\n}): Promise<void> {\n const projectDir = await projectRoot(true, cwd);\n\n if (prod) {\n process.env.NODE_ENV = \"production\";\n }\n\n const targetDirs = await findMonoRepoPackages(projectDir);\n\n await Promise.all(\n targetDirs.map(async (dir) => {\n const envFile = join(\n dir,\n secrets ? `.env${prod ? \".production\" : \"\"}.local` : `.env${prod ? \".production\" : \"\"}`,\n );\n\n let { parsed: currentEnv } = (await exists(envFile))\n ? config({\n path: envFile,\n logLevel: \"error\",\n quiet: true,\n })\n : { parsed: {} };\n\n if (!currentEnv) {\n currentEnv = {};\n }\n\n const prunedEnv = pruneCurrentEnv(currentEnv, env);\n const mergedEnv = deepmerge(prunedEnv, env as Record<string, unknown>);\n await writeFile(envFile, stringify(mergedEnv));\n }),\n );\n}\n\nconst quote = /[\\s\"'#]/;\n\nfunction stringifyPair([key, val]: [string, unknown]): string | undefined {\n if (val === undefined) {\n return undefined;\n }\n if (val === null) {\n return `${key}=\"\"`;\n }\n\n const type = typeof val;\n if (type === \"string\") {\n return `${key}=${quote.test(val as string) ? JSON.stringify(val) : val}`;\n }\n if (type === \"boolean\" || type === \"number\") {\n return `${key}=${val}`;\n }\n if (type === \"object\") {\n return `${key}=${JSON.stringify(val)}`;\n }\n\n throw new Error(`Unsupported type for key \"${key}\": ${type}`);\n}\n\nfunction stringify(obj: Record<string, unknown>): string {\n return Object.entries(obj)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(stringifyPair)\n .filter((value) => value !== undefined)\n .join(\"\\n\");\n}\n\n/**\n * Prunes the current environment variables from the new environment variables\n *\n * @param currentEnv - The current environment variables\n * @param env - The new environment variables\n * @returns The new environment variables with the current environment variables removed\n */\nfunction pruneCurrentEnv(currentEnv: Record<string, unknown>, env: Partial<DotEnv>): Record<string, unknown> {\n const dotEnvKeys = Object.keys(DotEnvSchema.shape);\n return Object.fromEntries(\n Object.entries(currentEnv).filter(([key]) => {\n if (dotEnvKeys.includes(key) && !env[key as keyof typeof env]) {\n return false;\n }\n return true;\n }),\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAMA,SAAgB,cAAc;AAC5B,KAAI,QAAQ,IAAI,gCAAgC,QAAQ;AACtD,SAAO;;AAIT,KAAI,QAAQ,IAAI,cAAc,QAAQ,IAAI,WAAW,QAAQ,IAAI,OAAO;AACtE,SAAO;;AAET,QAAO;;;;;;;;;;;;;;;ACFT,MAAa,cAAoB;AAC/B,KAAI,CAAC,aAAa,EAAE;AAClB;;AAEF,SAAQ,mCACQ;;;;;;EAMhB,CACC;;;;;;;;;;;;;;;;;ACbH,MAAa,cAAc,WAA2B;AACpD,QAAO,OAAO,QAAQ,kCAAkC,MAAM;;;;;;;;;ACNhE,IAAa,cAAb,cAAiC,MAAM;;;;;;;;;;;;;;AAevC,MAAa,UAAU,QAAuB;AAC5C,SAAQ,IAAI,GAAG;AACf,SAAQ,wDAAsB,WAAW,IAAI,CAAC,CAAC,CAAC;AAChD,SAAQ,IAAI,GAAG;AACf,OAAM,IAAI,YAAY,IAAI;;;;;;;;;ACX5B,IAAa,eAAb,cAAkC,MAAM;;;;;;;CAOtC,YACE,SACA,AAAgBA,MAChB,AAAgBC,QAChB;AACA,QAAM,QAAQ;EAHE;EACA;;;;;;AASpB,SAAS,cAAuB;AAC9B,QAAO,CAAC,EAAE,QAAQ,IAAI,cAAc,QAAQ,IAAI,WAAW,QAAQ,IAAI;;;;;;;;;;;;;;;;;;;;;;;AAwBzE,eAAsB,eACpB,SACA,MACA,SACmB;CACnB,MAAM,EAAE,OAAQ,GAAG,iBAAiB,WAAW,EAAE;CACjD,MAAM,YAAY,aAAa;CAE/B,MAAM,uBAAuB,YAAY,WAAW,QAAQ,CAAC,CAAC;CAE9D,MAAM,sCAAc,SAAS,MAAM;EAAE,GAAG;EAAc,KAAK;GAAE,GAAG,QAAQ;GAAK,GAAG,SAAS;GAAK;EAAE,CAAC;AACjG,SAAQ,MAAM,KAAK,MAAM,MAAM;CAC/B,MAAMA,SAAmB,EAAE;CAC3B,MAAMC,eAAyB,EAAE;CACjC,MAAMC,eAAyB,EAAE;AAEjC,QAAO,IAAI,SAAS,SAAS,WAAW;AACtC,QAAM,OAAO,GAAG,SAAS,SAA0B;GACjD,MAAM,aAAa,WAAW,KAAK,UAAU,CAAC;AAC9C,OAAI,CAAC,sBAAsB;AACzB,YAAQ,OAAO,MAAM,WAAW;;AAElC,UAAO,KAAK,WAAW;AACvB,gBAAa,KAAK,WAAW;IAC7B;AACF,QAAM,OAAO,GAAG,SAAS,SAA0B;GACjD,MAAM,aAAa,WAAW,KAAK,UAAU,CAAC;AAC9C,OAAI,CAAC,sBAAsB;AACzB,YAAQ,OAAO,MAAM,WAAW;;AAElC,UAAO,KAAK,WAAW;AACvB,gBAAa,KAAK,WAAW;IAC7B;EAEF,MAAM,wBAAwB;AAE5B,OAAI,aAAa,wBAAwB,OAAO,SAAS,GAAG;AAE1D,QAAI,aAAa,SAAS,GAAG;AAC3B,aAAQ,OAAO,MAAM,aAAa,KAAK,GAAG,CAAC;;AAE7C,QAAI,aAAa,SAAS,GAAG;AAC3B,aAAQ,OAAO,MAAM,aAAa,KAAK,GAAG,CAAC;;;;AAKjD,QAAM,GAAG,UAAU,QAAQ;AACzB,WAAQ,MAAM,OAAO,MAAM,MAAM;AACjC,oBAAiB;AACjB,UAAO,IAAI,aAAa,IAAI,SAAS,UAAU,OAAO,OAAO,IAAI,SAAS,WAAW,IAAI,OAAO,GAAG,OAAO,CAAC;IAC3G;AACF,QAAM,GAAG,UAAU,SAAS;AAC1B,WAAQ,MAAM,OAAO,MAAM,MAAM;AACjC,OAAI,SAAS,KAAK,SAAS,QAAQ,SAAS,KAAK;AAC/C,YAAQ,OAAO;AACf;;AAGF,oBAAiB;AACjB,UAAO,IAAI,aAAa,YAAY,QAAQ,qBAAqB,QAAQ,MAAM,OAAO,CAAC;IACvF;GACF;;;;;;;;;;;;;;;;AC1GJ,MAAa,SAAS,QAAsB;AAC1C,KAAI,CAAC,aAAa,EAAE;AAClB;;AAEF,SAAQ,IAAI,GAAG;AACf,SAAQ,mCAAkB,WAAW,IAAI,CAAC,CAAC;AAC3C,SAAQ,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACWjB,MAAa,QAAQ,SAAyB,QAAmC,WAAiB;CAChG,IAAIC;CACJ,IAAIC;AAEJ,KAAI,mBAAmB,OAAO;AAC5B,WAAS;AACT,gBAAc,QAAQ;AAEtB,MAAI,UAAU,WAAW,QAAQ,OAAO;AACtC,iBAAc,GAAG,YAAY,MAAM,QAAQ;;QAExC;AACL,gBAAc;;CAGhB,MAAM,gBAAgB,WAAW,YAAY;CAC7C,MAAM,eAAe,QAAQ,IAAI,cAAc,QAAQ,IAAI,WAAW,QAAQ,IAAI;AAGlF,KAAI,UAAU,UAAU,UAAU,SAAS;AACzC,UAAQ,IAAI,GAAG;AACf,MAAI,UAAU,QAAQ;GAEpB,MAAM,iBAAiB,cAAc,SAAS,QAAU,GAAG,8CAA6B,cAAc;AACtG,WAAQ,KAAK,eAAe;SACvB;GAEL,MAAM,iBAAiB,cAAc,SAAS,QAAU,GAAG,2CAA0B,cAAc;AACnG,WAAQ,MAAM,eAAe;;AAE/B;;AAIF,KAAI,CAAC,aAAa,EAAE;AAClB;;AAGF,SAAQ,IAAI,GAAG;AACf,SAAQ,IAAI,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;ACjD5B,SAAgB,KAAK,OAAe,OAAiC;CACnE,MAAM,eAAe,YAA4C;AAC/D,SAAOC,QACJ,KAAK,SAAS;AACb,OAAI,MAAM,QAAQ,KAAK,EAAE;AACvB,WAAO,KAAK,KAAK,YAAY,SAAS,UAAU,CAAC,KAAK,KAAK;;AAE7D,UAAO,OAAO;IACd,CACD,KAAK,KAAK;;AAGf,QAAO,KAAK,GAAG,MAAM,OAAO,YAAY,MAAM,GAAG;;;;;;;;;;;;;;;;ACnBnD,MAAa,SAAS,QAAsB;AAC1C,KAAI,CAAC,aAAa,EAAE;AAClB;;AAEF,SAAQ,IAAI,GAAG;AACf,SAAQ,0DAAwB,WAAW,IAAI,CAAC,CAAC,CAAC;AAClD,SAAQ,IAAI,GAAG;;;;;;;;;ACVjB,IAAa,eAAb,cAAkC,MAAM;CACtC,YACE,SACA,AAAgBC,eAChB;AACA,QAAM,QAAQ;EAFE;AAGhB,OAAK,OAAO;;;;;;;;;;;;;;;;;;;;;;;;AAqChB,MAAa,UAAU,OAAU,YAA2C;CAC1E,MAAM,eAAe,UAAiB;AACpC,OAAK,OAAO,QAAQ;EACpB,MAAM,eAAe,WAAW,MAAM,QAAQ;AAC9C,QAAM,IAAI,aAAa,cAAc,MAAM;;AAE7C,KAAIC,oBAAU,CAAC,aAAa,EAAE;AAC5B,MAAI;AACF,UAAO,MAAM,QAAQ,MAAM;WACpB,KAAK;AACZ,UAAO,YAAY,IAAa;;;CAGpC,MAAMC,uCAAuB,EAAE,QAAQ,QAAQ,QAAQ,CAAC,CAAC,MAAM,QAAQ,aAAa;AACpF,KAAI;EACF,MAAM,SAAS,MAAM,QAAQ,KAAKA,UAAQ;AAC1C,YAAQ,QAAQ,QAAQ,YAAY;AAGpC,QAAM,IAAI,SAAS,YAAY,QAAQ,SAAS,QAAQ,CAAC;AACzD,SAAO;UACA,KAAK;AACZ,YAAQ,iCAAgB,GAAG,QAAQ,aAAa,aAAa,CAAC;AAC9D,SAAO,YAAY,IAAa;;;;;;;;;;;;;;;;;;ACjEpC,SAAgB,sBAAsB,KAAa;AACjD,QAAO,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC,aAAa,GAAG,OAAO,IAAI,CAAC,MAAM,EAAE;;;;;;;;;;;;;;AAenE,SAAgB,iBAAiB,GAAW;CAC1C,MAAM,SAAS,EAAE,QAAQ,mBAAmB,QAAQ;CACpD,MAAM,aAAa,OAAO,QAAQ,mBAAmB,QAAQ;CAC7D,MAAM,cAAc,sBAAsB,WAAW;AACrD,QAAO,YAAY,QAAQ,QAAQ,IAAI,CAAC,MAAM;;;;;;;;;;;;;;AAehD,SAAgB,uCAAuC,GAAW;AAChE,QAAO,EAAE,QAAQ,SAAS,IAAI;;;;;;;;;;;;;;;AAgBhC,SAAgB,SAAS,OAAe,WAAmB;AACzD,KAAI,MAAM,UAAU,WAAW;AAC7B,SAAO;;AAET,QAAO,GAAG,MAAM,MAAM,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;AChDtC,SAAgB,MAAM,OAAe,MAAuB;AAC1D,KAAI,CAAC,aAAa,EAAE;AAClB;;AAGF,MAAK,MAAM;AAEX,KAAI,CAAC,QAAQ,KAAK,WAAW,GAAG;AAC9B,OAAK,qBAAqB;AAC1B;;CAGF,MAAM,aAAa,OAAO,KAAK,KAAK,GAA8B;CAClE,MAAMC,UAAQ,IAAIC,4BAAM,EACtB,SAAS,WAAW,KAAK,SAAS;EAChC,MAAM;EACN,oCAAmB,iBAAiB,IAAI,CAAC;EACzC,WAAW;EACZ,EAAE,EACJ,CAAC;AAEF,SAAM,QAAQ,KAAmC;AACjD,SAAM,YAAY;;;;;;;;;;;;;;;;;;AC3BpB,SAAgB,SAA4B,QAAW,OAA8B;AACnF,KAAI;AACF,SAAO,OAAO,MAAM,MAAM;UACnB,OAAO;AACd,MAAI,iBAAiBC,cAAU;GAC7B,MAAM,kBAAkB,MAAM,OAAO,KAAK,QAAQ,KAAK,IAAI,KAAK,KAAK,IAAI,CAAC,IAAI,IAAI,UAAU,CAAC,KAAK,KAAK;AACvG,SAAM,IAAI,MAAM,mBAAmB,MAAM,OAAO,SAAS,IAAI,MAAM,GAAG,KAAK,kBAAkB;;AAE/F,QAAM;;;;;;;;;;ACjBV,MAAaC,+BAA0CC,MAAE,QAAQ,CAAC,MAAM,cAAc;;;;;AAOtF,MAAaC,4BAAuCD,MAAE,QAAQ,CAAC,MAAM,cAAc;;;;;AAOnF,MAAaE,oBAA+BF,MAAE,QAAQ,CAAC,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;ACEvF,SAAgB,aAAgB,OAAe,eAAyB,MAAgB;AACtF,KAAI;EACF,MAAM,SAAS,KAAK,MAAM,MAAM;AAChC,MAAI,WAAW,aAAa,WAAW,MAAM;AAC3C,UAAO;;AAET,SAAO;UACA,MAAM;AAEb,SAAO;;;;;;;;;;;;;;;;;AAkBX,SAAgB,kBAAqB,OAAyB;AAC5D,KAAI,MAAM,SAAS,KAAM;AACvB,QAAM,IAAI,MAAM,iBAAiB;;CAEnC,MAAM,SAAS,gBAAgB,KAAK,MAAM;AAC1C,KAAI,CAAC,QAAQ;AACX,SAAO;;AAET,QAAO,aAAgB,OAAO,GAAG;;;;;;;;;;;;;;AAenC,SAAgB,sBAAyB,OAAmB;AAC1D,KAAI,UAAU,aAAa,UAAU,MAAM;AACzC,SAAO;;AAET,QAAO,aACL,KAAK,UACH,QACC,GAAG,YAAW,OAAOG,YAAU,WAAWA,QAAM,UAAU,GAAGA,QAC/D,CACF;;;;;;;;;;;;;;;;;;;;;AC/DH,MAAa,mBAAmBC,MAAE,QAAQ,CAAC,MAAM,eAAe;;;;;;;;;;;;;;;;;;ACFhE,MAAa,YAAYC,MAAE,QAAQ,CAAC,KAAK;;;;;;;;;;;;;;;AAiBzC,MAAa,gBAAgBA,MAAE,QAAQ,CAAC,MAAM,mDAAmD,EAC/F,SAAS,4GACV,CAAC;;;;;;;;;;;;;;;AAkBF,MAAa,kBAAkBA,MAAE,MAAM,CAAC,WAAW,cAAc,CAAC;;;;;;;AC5ClE,MAAa,sBAAsB;;;;AAInC,MAAa,iBAAiB;;;;;;AAO9B,MAAa,eAAeC,MAAE,OAAO;CAEnC,qBAAqBA,MAClB,MAAM;EAAC;EAAWA,MAAE,QAAQ,oBAAoB;EAAEA,MAAE,QAAQ,eAAe;EAAC,CAAC,CAC7E,QAAQ,iCAAiC;CAE5C,yBAAyB,6BAA6B,UAAU;CAEhE,kCAAkC,0BAA0B,UAAU;CAEtE,sBAAsB,iBAAiB,UAAU;CAEjD,wBAAwB,iBAAiB,UAAU;CAEnD,+BAA+B,iBAAiB,UAAU;CAE1D,wCAAwCA,MAAE,QAAQ,CAAC,UAAU;CAE7D,4BAA4B,iBAAiB,UAAU;CAEvD,8CAA8C,UAAU,UAAU;CAElE,6CAA6C,iBAAiB,UAAU;CAExE,+DAA+D,UAAU,UAAU;CAEnF,mBAAmB,iBAAiB,UAAU;CAE9C,4BAA4B,UAAU,UAAU;CAEhD,gCAAgCA,MAAE,QAAQ,CAAC,UAAU;CAErD,gCAAgCA,MAAE,QAAQ,CAAC,UAAU;CAErD,qBAAqB,iBAAiB,UAAU;CAEhD,yCAAyCA,MAAE,YACxC,UAAU,aAAa,OAAiB,EAAE,CAAC,EAC5CA,MAAE,MAAM,UAAU,CAAC,UAAU,CAC9B;CAED,sCAAsCA,MAAE,QAAQ,CAAC,UAAU;CAE3D,mBAAmB,iBAAiB,UAAU;CAE9C,oCAAoC,UAAU,UAAU;CAExD,iCAAiC,UAAU,UAAU;CAErD,+BAA+B,UAAU,UAAU;CAEnD,2BAA2B,iBAAiB,UAAU;CAEtD,6CAA6CA,MAAE,QAAQ,CAAC,UAAU;CAElE,mCAAmC,iBAAiB,UAAU;CAE9D,kBAAkB,iBAAiB,UAAU;CAE7C,2BAA2B,UAAU,UAAU;CAE/C,6BAA6BA,MAAE,QAAQ,CAAC,UAAU;CAElD,6BAA6BA,MAAE,QAAQ,CAAC,UAAU;CAElD,iBAAiB,iBAAiB,UAAU;CAE5C,8BAA8B,UAAU,UAAU;CAElD,kCAAkC,UAAU,UAAU;CAEtD,kCAAkC,UAAU,UAAU;CAEtD,8BAA8B,iBAAiB,UAAU;CAEzD,uCAAuC,UAAU,UAAU;CAE3D,uBAAuB,iBAAiB,UAAU;CAElD,wCAAwC,UAAU,UAAU;CAE5D,mCAAmC,UAAU,UAAU;CAEvD,6BAA6BA,MAAE,QAAQ,CAAC,UAAU;CAElD,sBAAsBA,MAAE,KAAK;EAAC;EAAS;EAAQ;EAAQ;EAAS;EAAO,CAAC,CAAC,QAAQ,OAAO;CACzF,CAAC;;;;;AAWF,MAAa,sBAAsB,aAAa,SAAS;;;;;;;;;;;;;;;;;;ACrGzD,MAAa,WAAWC,MAAE,MAAM,CAC9BA,MACG,QAAQ,CACR,MAAM,EACTA,MACG,QAAQ,CACR,MAAM,oBAAoB,CAC9B,CAAC;;;;;;;;;;;;;;;;;;;;;;;ACCF,eAAsB,QACpB,aACA,MACA,OAAe,QAAQ,KAAK,EACsB;AAClD,QAAO,mBAAmB,aAAa,CAAC,CAAC,MAAM,KAAK;;AAGtD,eAAe,mBACb,aACA,MACA,OAAe,QAAQ,KAAK,EACsB;AAClD,KAAI,MAAM;AACR,UAAQ,IAAI,WAAW;;CAEzB,IAAI,EAAE,yCAAkB;EACtB,YAAY;EACZ,UAAU;EACV,UAAU;EACV,OAAO;EACP,MAAM,qBAAM,MAAM,OAAO,sBAAO,MAAM,aAAa,CAAC;EACrD,CAAC;AAEF,KAAI,CAAC,QAAQ;AACX,WAAS,EAAE;;CAEb,MAAM,aAAa,OAAO,YACxB,OAAO,QAAQ,QAAQ,IAAI,CAAC,QAAQ,CAAC,GAAG,WAAW,OAAO,UAAU,YAAY,UAAU,GAAG,CAC9F;AACD,KAAI;AACF,SAAO,SAAS,cAAc,eAAe,qBAAqB;GAChE,GAAG;GACH,GAAG;GACJ,CAAC;UACK,OAAO;AACd,SAAQ,MAAgB,QAAQ;AAChC,SAAO,EAAE;;;;;;;;;;;;;;;;;;;;AC5Cb,eAAsB,YAAY,gBAAgB,OAAO,KAA+B;CACtF,MAAM,kBAAkB,0BAAa,gBAAgB,EAAE,KAAK,CAAC;AAC7D,KAAI,CAAC,iBAAiB;AACpB,MAAI,eAAe;AACjB,UAAO,QAAQ,KAAK;;AAEtB,QAAM,IAAI,MAAM,sDAAsD;;AAExE,+BAAe,gBAAgB;;;;;;;;;;;;;;;;;;ACTjC,eAAsB,OAAO,MAAkC;AAC7D,KAAI;AACF,mCAAW,KAAK;AAChB,SAAO;SACD;AACN,SAAO;;;;;;;;;;;;;;;;;ACHX,eAAsB,iBAAiB,UAA0C;CAC/E,MAAM,eAAe,0BAAa;EAAC;EAAqB;EAAa;EAAkB;EAAa;EAAW,EAAE,EAC/G,KAAK,UACN,CAAC;AACF,KAAI,cAAc;EAChB,MAAM,6DAA+B,aAAa,EAAE,eAAe;EACnE,MAAM,gBAAgB,MAAM,yBAAyB,gBAAgB;AACrE,SAAO,uCAAwB,aAAa,GAAG;;CAGjD,IAAI,aAAa;AAEjB,QAAO,eAAe,KAAK;EACzB,MAAM,sCAAuB,YAAY,eAAe;AAExD,MAAI,MAAM,yBAAyB,gBAAgB,EAAE;AACnD,UAAO;;EAGT,MAAM,mCAAoB,WAAW;AACrC,MAAI,cAAc,YAAY;AAC5B;;AAEF,eAAa;;AAGf,QAAO;;;;;;;;;;;;;AAcT,eAAsB,qBAAqB,YAAuC;AAChF,KAAI;EACF,MAAM,eAAe,MAAM,iBAAiB,WAAW;AACvD,MAAI,CAAC,cAAc;AACjB,UAAO,CAAC,WAAW;;EAGrB,MAAM,sCAAuB,cAAc,eAAe;EAC1D,MAAM,cAAc,aAAuC,qCAAe,iBAAiB,QAAQ,CAAC;EACpG,MAAM,aAAa,aAAa,cAAc,EAAE;EAEhD,MAAM,eAAe,MAAM,QAAQ,IACjC,WAAW,IAAI,OAAO,cAAsB;GAC1C,MAAM,UAAU,yCAAgB,cAAc,WAAW,eAAe,CAAC;AACzE,UAAO,QAAQ,KAAK,8BAAe,OAAO,KAAK,CAAC;IAChD,CACH;EAED,MAAM,WAAW,aAAa,MAAM;AAEpC,SAAO,SAAS,WAAW,IAAI,CAAC,WAAW,GAAG,CAAC,cAAc,GAAG,SAAS;UAClE,QAAQ;AAEf,SAAO,CAAC,WAAW;;;AAIvB,eAAe,yBAAyB,iBAA2C;AACjF,KAAI,MAAM,OAAO,gBAAgB,EAAE;EACjC,MAAM,cAAc,aAAuC,qCAAe,iBAAiB,QAAQ,CAAC;AACpG,MAAI,aAAa,cAAc,MAAM,QAAQ,aAAa,WAAW,IAAI,aAAa,WAAW,SAAS,GAAG;AAC3G,UAAO;;;AAGX,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACpDT,eAAsB,SAAS,EAC7B,MACA,KACA,SACA,OAMgB;CAChB,MAAM,aAAa,MAAM,YAAY,MAAM,IAAI;AAE/C,KAAI,MAAM;AACR,UAAQ,IAAI,WAAW;;CAGzB,MAAM,aAAa,MAAM,qBAAqB,WAAW;AAEzD,OAAM,QAAQ,IACZ,WAAW,IAAI,OAAO,QAAQ;EAC5B,MAAM,8BACJ,KACA,UAAU,OAAO,OAAO,gBAAgB,GAAG,UAAU,OAAO,OAAO,gBAAgB,KACpF;EAED,IAAI,EAAE,QAAQ,eAAgB,MAAM,OAAO,QAAQ,iCACxC;GACL,MAAM;GACN,UAAU;GACV,OAAO;GACR,CAAC,GACF,EAAE,QAAQ,EAAE,EAAE;AAElB,MAAI,CAAC,YAAY;AACf,gBAAa,EAAE;;EAGjB,MAAM,YAAY,gBAAgB,YAAY,IAAI;EAClD,MAAM,wCAAsB,WAAW,IAA+B;AACtE,wCAAgB,SAAS,UAAU,UAAU,CAAC;GAC9C,CACH;;AAGH,MAAM,QAAQ;AAEd,SAAS,cAAc,CAAC,KAAK,MAA6C;AACxE,KAAI,QAAQ,WAAW;AACrB,SAAO;;AAET,KAAI,QAAQ,MAAM;AAChB,SAAO,GAAG,IAAI;;CAGhB,MAAM,OAAO,OAAO;AACpB,KAAI,SAAS,UAAU;AACrB,SAAO,GAAG,IAAI,GAAG,MAAM,KAAK,IAAc,GAAG,KAAK,UAAU,IAAI,GAAG;;AAErE,KAAI,SAAS,aAAa,SAAS,UAAU;AAC3C,SAAO,GAAG,IAAI,GAAG;;AAEnB,KAAI,SAAS,UAAU;AACrB,SAAO,GAAG,IAAI,GAAG,KAAK,UAAU,IAAI;;AAGtC,OAAM,IAAI,MAAM,6BAA6B,IAAI,KAAK,OAAO;;AAG/D,SAAS,UAAU,KAAsC;AACvD,QAAO,OAAO,QAAQ,IAAI,CACvB,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,CAAC,CACtC,IAAI,cAAc,CAClB,QAAQ,UAAU,UAAU,UAAU,CACtC,KAAK,KAAK;;;;;;;;;AAUf,SAAS,gBAAgB,YAAqC,KAA+C;CAC3G,MAAM,aAAa,OAAO,KAAK,aAAa,MAAM;AAClD,QAAO,OAAO,YACZ,OAAO,QAAQ,WAAW,CAAC,QAAQ,CAAC,SAAS;AAC3C,MAAI,WAAW,SAAS,IAAI,IAAI,CAAC,IAAI,MAA0B;AAC7D,UAAO;;AAET,SAAO;GACP,CACH"}
1
+ {"version":3,"file":"environment.cjs","names":["code: number","output: string[]","stdoutOutput: string[]","stderrOutput: string[]","text: string","items","originalError: Error","isInCi","spinner","table","Table","ZodError","ApplicationAccessTokenSchema: ZodString","z","PersonalAccessTokenSchema: ZodString","AccessTokenSchema: ZodString","value","z","z","z","z"],"sources":["../src/terminal/should-print.ts","../src/terminal/ascii.ts","../src/logging/mask-tokens.ts","../src/terminal/cancel.ts","../src/terminal/execute-command.ts","../src/terminal/intro.ts","../src/terminal/note.ts","../src/terminal/list.ts","../src/terminal/outro.ts","../src/terminal/spinner.ts","../src/string.ts","../src/terminal/table.ts","../src/validation/validate.ts","../src/validation/access-token.schema.ts","../src/json.ts","../src/validation/unique-name.schema.ts","../src/validation/url.schema.ts","../src/validation/dot-env.schema.ts","../src/validation/id.schema.ts","../src/environment/load-env.ts","../src/filesystem/project-root.ts","../src/filesystem/exists.ts","../src/filesystem/mono-repo.ts","../src/environment/write-env.ts"],"sourcesContent":["/**\n * Determines whether terminal output should be printed based on environment variables.\n *\n * **Environment Variable Precedence:**\n * 1. `SETTLEMINT_DISABLE_TERMINAL=\"true\"` - Completely disables all terminal output (highest priority)\n * 2. `CLAUDECODE`, `REPL_ID`, or `AGENT` (any truthy value) - Enables quiet mode, suppressing info/debug/status messages\n *\n * **Quiet Mode Behavior:**\n * When quiet mode is active (Claude Code environments), this function returns `false` to suppress\n * informational output. However, warnings and errors are always displayed regardless of quiet mode,\n * as they are handled separately in the `note()` function with level-based filtering.\n *\n * @returns `true` if terminal output should be printed, `false` if suppressed\n */\nexport function shouldPrint(): boolean {\n if (process.env.SETTLEMINT_DISABLE_TERMINAL === \"true\") {\n return false;\n }\n // In quiet mode (Claude Code), suppress info/debug/status messages\n // Warnings and errors will still be displayed via note() with appropriate levels\n if (process.env.CLAUDECODE || process.env.REPL_ID || process.env.AGENT) {\n return false;\n }\n return true;\n}\n","import { magentaBright } from \"yoctocolors\";\nimport { shouldPrint } from \"./should-print.js\";\n\n/**\n * Prints the SettleMint ASCII art logo to the console in magenta color.\n * Used for CLI branding and visual identification.\n *\n * @example\n * import { ascii } from \"@settlemint/sdk-utils/terminal\";\n *\n * // Prints the SettleMint logo\n * ascii();\n */\nexport const ascii = (): void => {\n if (!shouldPrint()) {\n return;\n }\n console.log(\n magentaBright(`\n _________ __ __ .__ _____ .__ __\n / _____/ _____/ |__/ |_| | ____ / \\\\ |__| _____/ |_\n \\\\_____ \\\\_/ __ \\\\ __\\\\ __\\\\ | _/ __ \\\\ / \\\\ / \\\\| |/ \\\\ __\\\\\n / \\\\ ___/| | | | | |_\\\\ ___// Y \\\\ | | \\\\ |\n/_________/\\\\_____>__| |__| |____/\\\\_____>____|____/__|___|__/__|\n`),\n );\n};\n","/**\n * Masks sensitive SettleMint tokens in output text by replacing them with asterisks.\n * Handles personal access tokens (PAT), application access tokens (AAT), and service account tokens (SAT).\n *\n * @param output - The text string that may contain sensitive tokens\n * @returns The text with any sensitive tokens masked with asterisks\n * @example\n * import { maskTokens } from \"@settlemint/sdk-utils/terminal\";\n *\n * // Masks a token in text\n * const masked = maskTokens(\"Token: sm_pat_****\"); // \"Token: ***\"\n */\nexport const maskTokens = (output: string): string => {\n return output.replace(/sm_(pat|aat|sat)_[0-9a-zA-Z]+/g, \"***\");\n};\n","import { maskTokens } from \"@/logging/mask-tokens.js\";\nimport { inverse, redBright } from \"yoctocolors\";\n\n/**\n * Error class used to indicate that the operation was cancelled.\n * This error is used to signal that the operation should be aborted.\n */\nexport class CancelError extends Error {}\n\n/**\n * Displays an error message in red inverse text and throws a CancelError.\n * Used to terminate execution with a visible error message.\n * Any sensitive tokens in the message are masked before display.\n *\n * @param msg - The error message to display\n * @returns never - Function does not return as it throws an error\n * @example\n * import { cancel } from \"@settlemint/sdk-utils/terminal\";\n *\n * // Exits process with error message\n * cancel(\"An error occurred\");\n */\nexport const cancel = (msg: string): never => {\n console.log(\"\");\n console.log(inverse(redBright(maskTokens(msg))));\n console.log(\"\");\n throw new CancelError(msg);\n};\n","import { type SpawnOptionsWithoutStdio, spawn } from \"node:child_process\";\nimport { maskTokens } from \"../logging/mask-tokens.js\";\n\n/**\n * Options for executing a command, extending SpawnOptionsWithoutStdio\n */\nexport interface ExecuteCommandOptions extends SpawnOptionsWithoutStdio {\n /** Whether to suppress output to stdout/stderr */\n silent?: boolean;\n}\n\n/**\n * Error class for command execution errors\n * @extends Error\n */\nexport class CommandError extends Error {\n /**\n * Constructs a new CommandError\n * @param message - The error message\n * @param code - The exit code of the command\n * @param output - The output of the command\n */\n constructor(\n message: string,\n public readonly code: number,\n public readonly output: string[],\n ) {\n super(message);\n }\n}\n\n/**\n * Checks if we're in quiet mode (Claude Code environment)\n */\nfunction isQuietMode(): boolean {\n return !!(process.env.CLAUDECODE || process.env.REPL_ID || process.env.AGENT);\n}\n\n/**\n * Executes a command with the given arguments in a child process.\n * Pipes stdin to the child process and captures stdout/stderr output.\n * Masks any sensitive tokens in the output before displaying or returning.\n * In quiet mode (when CLAUDECODE, REPL_ID, or AGENT env vars are set),\n * output is suppressed unless the command errors out.\n *\n * @param command - The command to execute\n * @param args - Array of arguments to pass to the command\n * @param options - Options for customizing command execution\n * @returns Array of output strings from stdout and stderr\n * @throws {CommandError} If the process fails to start or exits with non-zero code\n * @example\n * import { executeCommand } from \"@settlemint/sdk-utils/terminal\";\n *\n * // Execute git clone\n * await executeCommand(\"git\", [\"clone\", \"repo-url\"]);\n *\n * // Execute silently\n * await executeCommand(\"npm\", [\"install\"], { silent: true });\n */\nexport async function executeCommand(\n command: string,\n args: string[],\n options?: ExecuteCommandOptions,\n): Promise<string[]> {\n const { silent, ...spawnOptions } = options ?? {};\n const quietMode = isQuietMode();\n // In quiet mode, suppress output unless explicitly overridden with silent: false\n const shouldSuppressOutput = quietMode ? silent !== false : !!silent;\n\n const child = spawn(command, args, { ...spawnOptions, env: { ...process.env, ...options?.env } });\n process.stdin.pipe(child.stdin);\n const output: string[] = [];\n const stdoutOutput: string[] = [];\n const stderrOutput: string[] = [];\n\n return new Promise((resolve, reject) => {\n child.stdout.on(\"data\", (data: Buffer | string) => {\n const maskedData = maskTokens(data.toString());\n if (!shouldSuppressOutput) {\n process.stdout.write(maskedData);\n }\n output.push(maskedData);\n stdoutOutput.push(maskedData);\n });\n child.stderr.on(\"data\", (data: Buffer | string) => {\n const maskedData = maskTokens(data.toString());\n if (!shouldSuppressOutput) {\n process.stderr.write(maskedData);\n }\n output.push(maskedData);\n stderrOutput.push(maskedData);\n });\n\n const showErrorOutput = () => {\n // In quiet mode, show output on error\n if (quietMode && shouldSuppressOutput && output.length > 0) {\n // Write stdout to stdout and stderr to stderr\n if (stdoutOutput.length > 0) {\n process.stdout.write(stdoutOutput.join(\"\"));\n }\n if (stderrOutput.length > 0) {\n process.stderr.write(stderrOutput.join(\"\"));\n }\n }\n };\n\n child.on(\"error\", (err) => {\n process.stdin.unpipe(child.stdin);\n showErrorOutput();\n reject(new CommandError(err.message, \"code\" in err && typeof err.code === \"number\" ? err.code : 1, output));\n });\n child.on(\"close\", (code) => {\n process.stdin.unpipe(child.stdin);\n if (code === 0 || code === null || code === 143) {\n resolve(output);\n return;\n }\n // In quiet mode, show output on error\n showErrorOutput();\n reject(new CommandError(`Command \"${command}\" exited with code ${code}`, code, output));\n });\n });\n}\n","import { maskTokens } from \"@/logging/mask-tokens.js\";\nimport { magentaBright } from \"yoctocolors\";\nimport { shouldPrint } from \"./should-print.js\";\n\n/**\n * Displays an introductory message in magenta text with padding.\n * Any sensitive tokens in the message are masked before display.\n *\n * @param msg - The message to display as introduction\n * @example\n * import { intro } from \"@settlemint/sdk-utils/terminal\";\n *\n * // Display intro message\n * intro(\"Starting deployment...\");\n */\nexport const intro = (msg: string): void => {\n if (!shouldPrint()) {\n return;\n }\n console.log(\"\");\n console.log(magentaBright(maskTokens(msg)));\n console.log(\"\");\n};\n","import { maskTokens } from \"@/logging/mask-tokens.js\";\nimport { redBright, yellowBright } from \"yoctocolors\";\nimport { shouldPrint } from \"./should-print.js\";\n\n/**\n * Applies color to a message if not already colored.\n * @param msg - The message to colorize\n * @param level - The severity level determining the color\n * @returns Colorized message (yellow for warnings, red for errors, unchanged for info)\n */\nfunction colorize(msg: string, level: \"info\" | \"warn\" | \"error\"): string {\n // Don't re-colorize messages that already contain ANSI escape codes\n if (msg.includes(\"\\u001b[\")) {\n return msg;\n }\n if (level === \"warn\") {\n return yellowBright(msg);\n }\n if (level === \"error\") {\n return redBright(msg);\n }\n return msg;\n}\n\n/**\n * Determines whether a message should be printed based on its level and quiet mode.\n * @param level - The severity level of the message\n * @returns true if the message should be printed, false otherwise\n */\nfunction canPrint(level: \"info\" | \"warn\" | \"error\"): boolean {\n // Warnings and errors always print, even in quiet mode\n if (level !== \"info\") {\n return true;\n }\n // Info messages respect shouldPrint() which checks for quiet mode\n return shouldPrint();\n}\n\n/**\n * Prepares a message for display by converting Error objects and masking tokens.\n * @param value - The message string or Error object\n * @param level - The severity level (stack traces are included for errors)\n * @returns Masked message text, optionally with stack trace\n */\nfunction prepareMessage(value: string | Error, level: \"info\" | \"warn\" | \"error\"): string {\n let text: string;\n if (value instanceof Error) {\n text = value.message;\n // For errors, automatically include stack trace\n if (level === \"error\" && value.stack) {\n text = `${text}\\n\\n${value.stack}`;\n }\n } else {\n text = value;\n }\n return maskTokens(text);\n}\n\n/**\n * Displays a note message with optional warning or error level formatting.\n * Regular notes are displayed in normal text, warnings are shown in yellow, and errors in red.\n * Any sensitive tokens in the message are masked before display.\n * Warnings and errors are always displayed, even in quiet mode (when CLAUDECODE, REPL_ID, or AGENT env vars are set).\n * When an Error object is provided with level \"error\", the stack trace is automatically included.\n *\n * @param message - The message to display as a note. Can be either:\n * - A string: Displayed directly with appropriate styling\n * - An Error object: The error message is displayed, and for level \"error\", the stack trace is automatically included\n * @param level - The note level: \"info\" (default), \"warn\" for warning styling, or \"error\" for error styling\n * @example\n * import { note } from \"@settlemint/sdk-utils/terminal\";\n *\n * // Display info note\n * note(\"Operation completed successfully\");\n *\n * // Display warning note\n * note(\"Low disk space remaining\", \"warn\");\n *\n * // Display error note (string)\n * note(\"Operation failed\", \"error\");\n *\n * // Display error with stack trace automatically (Error object)\n * try {\n * // some operation\n * } catch (error) {\n * // If error is an Error object and level is \"error\", stack trace is included automatically\n * note(error, \"error\");\n * }\n */\nexport const note = (message: string | Error, level: \"info\" | \"warn\" | \"error\" = \"info\"): void => {\n if (!canPrint(level)) {\n return;\n }\n\n const msg = prepareMessage(message, level);\n console.log(\"\");\n\n if (level === \"warn\") {\n console.warn(colorize(msg, level));\n } else if (level === \"error\") {\n console.error(colorize(msg, level));\n } else {\n console.log(msg);\n }\n};\n","import { note } from \"./note.js\";\n\n/**\n * Displays a list of items in a formatted manner, supporting nested items.\n *\n * @param title - The title of the list\n * @param items - The items to display, can be strings or arrays for nested items\n * @returns The formatted list\n * @example\n * import { list } from \"@settlemint/sdk-utils/terminal\";\n *\n * // Simple list\n * list(\"Use cases\", [\"use case 1\", \"use case 2\", \"use case 3\"]);\n *\n * // Nested list\n * list(\"Providers\", [\n * \"AWS\",\n * [\"us-east-1\", \"eu-west-1\"],\n * \"Azure\",\n * [\"eastus\", \"westeurope\"]\n * ]);\n */\nexport function list(title: string, items: Array<string | string[]>) {\n const formatItems = (items: Array<string | string[]>): string => {\n return items\n .map((item) => {\n if (Array.isArray(item)) {\n return item.map((subItem) => ` • ${subItem}`).join(\"\\n\");\n }\n return ` • ${item}`;\n })\n .join(\"\\n\");\n };\n\n return note(`${title}:\\n\\n${formatItems(items)}`);\n}\n","import { maskTokens } from \"@/logging/mask-tokens.js\";\nimport { shouldPrint } from \"@/terminal/should-print.js\";\nimport { greenBright, inverse } from \"yoctocolors\";\n\n/**\n * Displays a closing message in green inverted text with padding.\n * Any sensitive tokens in the message are masked before display.\n *\n * @param msg - The message to display as conclusion\n * @example\n * import { outro } from \"@settlemint/sdk-utils/terminal\";\n *\n * // Display outro message\n * outro(\"Deployment completed successfully!\");\n */\nexport const outro = (msg: string): void => {\n if (!shouldPrint()) {\n return;\n }\n console.log(\"\");\n console.log(inverse(greenBright(maskTokens(msg))));\n console.log(\"\");\n};\n","import isInCi from \"is-in-ci\";\nimport yoctoSpinner, { type Spinner } from \"yocto-spinner\";\nimport { redBright } from \"yoctocolors\";\nimport { note } from \"./note.js\";\nimport { shouldPrint } from \"./should-print.js\";\n\n/**\n * Error class used to indicate that the spinner operation failed.\n * This error is used to signal that the operation should be aborted.\n */\nexport class SpinnerError extends Error {\n constructor(\n message: string,\n public readonly originalError: Error,\n ) {\n super(message);\n this.name = \"SpinnerError\";\n }\n}\n\n/**\n * Options for configuring the spinner behavior\n */\nexport interface SpinnerOptions<R> {\n /** Message to display when spinner starts */\n startMessage: string;\n /** Async task to execute while spinner is active */\n task: (spinner?: Spinner) => Promise<R>;\n /** Message to display when spinner completes successfully */\n stopMessage: string;\n}\n\n/**\n * Displays a loading spinner while executing an async task.\n * Shows progress with start/stop messages and handles errors.\n * Spinner is disabled in CI environments.\n *\n * @param options - Configuration options for the spinner\n * @returns The result from the executed task\n * @throws Will exit process with code 1 if task fails\n * @example\n * import { spinner } from \"@settlemint/sdk-utils/terminal\";\n *\n * // Show spinner during async task\n * const result = await spinner({\n * startMessage: \"Deploying...\",\n * task: async () => {\n * // Async work here\n * return \"success\";\n * },\n * stopMessage: \"Deployed successfully!\"\n * });\n */\nexport const spinner = async <R>(options: SpinnerOptions<R>): Promise<R> => {\n const handleError = (error: Error) => {\n note(error, \"error\");\n throw new SpinnerError(error.message, error);\n };\n if (isInCi || !shouldPrint()) {\n try {\n return await options.task();\n } catch (err) {\n return handleError(err as Error);\n }\n }\n const spinner = yoctoSpinner({ stream: process.stdout }).start(options.startMessage);\n try {\n const result = await options.task(spinner);\n spinner.success(options.stopMessage);\n // Ensure spinner success message renders before proceeding to avoid\n // terminal output overlap issues with subsequent messages\n await new Promise((resolve) => process.nextTick(resolve));\n return result;\n } catch (err) {\n spinner.error(redBright(`${options.startMessage} --> Error!`));\n return handleError(err as Error);\n }\n};\n","/**\n * Capitalizes the first letter of a string.\n *\n * @param val - The string to capitalize\n * @returns The input string with its first letter capitalized\n *\n * @example\n * import { capitalizeFirstLetter } from \"@settlemint/sdk-utils\";\n *\n * const capitalized = capitalizeFirstLetter(\"hello\");\n * // Returns: \"Hello\"\n */\nexport function capitalizeFirstLetter(val: string) {\n return String(val).charAt(0).toUpperCase() + String(val).slice(1);\n}\n\n/**\n * Converts a camelCase string to a human-readable string.\n *\n * @param s - The camelCase string to convert\n * @returns The human-readable string\n *\n * @example\n * import { camelCaseToWords } from \"@settlemint/sdk-utils\";\n *\n * const words = camelCaseToWords(\"camelCaseString\");\n * // Returns: \"Camel Case String\"\n */\nexport function camelCaseToWords(s: string) {\n const result = s.replace(/([a-z])([A-Z])/g, \"$1 $2\");\n const withSpaces = result.replace(/([A-Z])([a-z])/g, \" $1$2\");\n const capitalized = capitalizeFirstLetter(withSpaces);\n return capitalized.replace(/\\s+/g, \" \").trim();\n}\n\n/**\n * Replaces underscores and hyphens with spaces.\n *\n * @param s - The string to replace underscores and hyphens with spaces\n * @returns The input string with underscores and hyphens replaced with spaces\n *\n * @example\n * import { replaceUnderscoresAndHyphensWithSpaces } from \"@settlemint/sdk-utils\";\n *\n * const result = replaceUnderscoresAndHyphensWithSpaces(\"Already_Spaced-Second\");\n * // Returns: \"Already Spaced Second\"\n */\nexport function replaceUnderscoresAndHyphensWithSpaces(s: string) {\n return s.replace(/[-_]/g, \" \");\n}\n\n/**\n * Truncates a string to a maximum length and appends \"...\" if it is longer.\n *\n * @param value - The string to truncate\n * @param maxLength - The maximum length of the string\n * @returns The truncated string or the original string if it is shorter than the maximum length\n *\n * @example\n * import { truncate } from \"@settlemint/sdk-utils\";\n *\n * const truncated = truncate(\"Hello, world!\", 10);\n * // Returns: \"Hello, wor...\"\n */\nexport function truncate(value: string, maxLength: number) {\n if (value.length <= maxLength) {\n return value;\n }\n return `${value.slice(0, maxLength)}...`;\n}\n","import { Table } from \"console-table-printer\";\nimport { whiteBright } from \"yoctocolors\";\nimport { camelCaseToWords } from \"@/string.js\";\nimport { note } from \"./note.js\";\nimport { shouldPrint } from \"./should-print.js\";\n/**\n * Displays data in a formatted table in the terminal.\n *\n * @param title - Title to display above the table\n * @param data - Array of objects to display in table format\n * @example\n * import { table } from \"@settlemint/sdk-utils/terminal\";\n *\n * const data = [\n * { name: \"Item 1\", value: 100 },\n * { name: \"Item 2\", value: 200 }\n * ];\n *\n * table(\"My Table\", data);\n */\nexport function table(title: string, data: unknown[]): void {\n if (!shouldPrint()) {\n return;\n }\n\n note(title);\n\n if (!data || data.length === 0) {\n note(\"No data to display\");\n return;\n }\n\n const columnKeys = Object.keys(data[0] as Record<string, unknown>);\n const table = new Table({\n columns: columnKeys.map((key) => ({\n name: key,\n title: whiteBright(camelCaseToWords(key)),\n alignment: \"left\",\n })),\n });\n // biome-ignore lint/suspicious/noExplicitAny: Data structure varies based on table content\n table.addRows(data as Array<Record<string, any>>);\n table.printTable();\n}\n","import { ZodError, type ZodType } from \"zod\";\n\n/**\n * Validates a value against a given Zod schema.\n *\n * @param schema - The Zod schema to validate against.\n * @param value - The value to validate.\n * @returns The validated and parsed value.\n * @throws Will throw an error if validation fails, with formatted error messages.\n *\n * @example\n * import { validate } from \"@settlemint/sdk-utils/validation\";\n *\n * const validatedId = validate(IdSchema, \"550e8400-e29b-41d4-a716-446655440000\");\n */\nexport function validate<T extends ZodType>(schema: T, value: unknown): T[\"_output\"] {\n try {\n return schema.parse(value);\n } catch (error) {\n if (error instanceof ZodError) {\n const formattedErrors = error.issues.map((err) => `- ${err.path.join(\".\")}: ${err.message}`).join(\"\\n\");\n throw new Error(`Validation error${error.issues.length > 1 ? \"s\" : \"\"}:\\n${formattedErrors}`);\n }\n throw error; // Re-throw if it's not a ZodError\n }\n}\n","import { type ZodString, z } from \"zod\";\n\n/**\n * Schema for validating application access tokens.\n * Application access tokens start with 'sm_aat_' prefix.\n */\nexport const ApplicationAccessTokenSchema: ZodString = z.string().regex(/^sm_aat_.+$/);\nexport type ApplicationAccessToken = z.infer<typeof ApplicationAccessTokenSchema>;\n\n/**\n * Schema for validating personal access tokens.\n * Personal access tokens start with 'sm_pat_' prefix.\n */\nexport const PersonalAccessTokenSchema: ZodString = z.string().regex(/^sm_pat_.+$/);\nexport type PersonalAccessToken = z.infer<typeof PersonalAccessTokenSchema>;\n\n/**\n * Schema for validating both application and personal access tokens.\n * Accepts tokens starting with either 'sm_pat_' or 'sm_aat_' prefix.\n */\nexport const AccessTokenSchema: ZodString = z.string().regex(/^(sm_pat_.+|sm_aat_.+)$/);\nexport type AccessToken = z.infer<typeof AccessTokenSchema>;\n","/**\n * Attempts to parse a JSON string into a typed value, returning a default value if parsing fails.\n *\n * @param value - The JSON string to parse\n * @param defaultValue - The value to return if parsing fails or results in null/undefined\n * @returns The parsed JSON value as type T, or the default value if parsing fails\n *\n * @example\n * import { tryParseJson } from \"@settlemint/sdk-utils\";\n *\n * const config = tryParseJson<{ port: number }>(\n * '{\"port\": 3000}',\n * { port: 8080 }\n * );\n * // Returns: { port: 3000 }\n *\n * const invalid = tryParseJson<string[]>(\n * 'invalid json',\n * []\n * );\n * // Returns: []\n */\nexport function tryParseJson<T>(value: string, defaultValue: T | null = null): T | null {\n try {\n const parsed = JSON.parse(value) as T;\n if (parsed === undefined || parsed === null) {\n return defaultValue;\n }\n return parsed;\n } catch (_err) {\n // Invalid json\n return defaultValue;\n }\n}\n\n/**\n * Extracts a JSON object from a string.\n *\n * @param value - The string to extract the JSON object from\n * @returns The parsed JSON object, or null if no JSON object is found\n * @throws {Error} If the input string is too long (longer than 5000 characters)\n * @example\n * import { extractJsonObject } from \"@settlemint/sdk-utils\";\n *\n * const json = extractJsonObject<{ port: number }>(\n * 'port info: {\"port\": 3000}',\n * );\n * // Returns: { port: 3000 }\n */\nexport function extractJsonObject<T>(value: string): T | null {\n if (value.length > 5000) {\n throw new Error(\"Input too long\");\n }\n const result = /\\{([\\s\\S]*)\\}/.exec(value);\n if (!result) {\n return null;\n }\n return tryParseJson<T>(result[0]);\n}\n\n/**\n * Converts a value to a JSON stringifiable format.\n *\n * @param value - The value to convert\n * @returns The JSON stringifiable value\n *\n * @example\n * import { makeJsonStringifiable } from \"@settlemint/sdk-utils\";\n *\n * const json = makeJsonStringifiable<{ amount: bigint }>({ amount: BigInt(1000) });\n * // Returns: '{\"amount\":\"1000\"}'\n */\nexport function makeJsonStringifiable<T>(value: unknown): T {\n if (value === undefined || value === null) {\n return value as T;\n }\n return tryParseJson<T>(\n JSON.stringify(\n value,\n (_, value) => (typeof value === \"bigint\" ? value.toString() : value), // return everything else unchanged\n ),\n ) as T;\n}\n","import { z } from \"zod\";\n\n/**\n * Schema for validating unique names used across the SettleMint platform.\n * Only accepts lowercase alphanumeric characters and hyphens.\n * Used for workspace names, application names, service names etc.\n *\n * @example\n * import { UniqueNameSchema } from \"@settlemint/sdk-utils/validation\";\n *\n * // Validate a workspace name\n * const isValidName = UniqueNameSchema.safeParse(\"my-workspace-123\").success;\n * // true\n *\n * // Invalid names will fail validation\n * const isInvalidName = UniqueNameSchema.safeParse(\"My Workspace!\").success;\n * // false\n */\nexport const UniqueNameSchema = z.string().regex(/^[a-z0-9-]+$/);\n\n/**\n * Type definition for unique names, inferred from UniqueNameSchema.\n */\nexport type UniqueName = z.infer<typeof UniqueNameSchema>;\n","import { z } from \"zod\";\n\n/**\n * Schema for validating URLs.\n *\n * @example\n * import { UrlSchema } from \"@settlemint/sdk-utils/validation\";\n *\n * // Validate a URL\n * const isValidUrl = UrlSchema.safeParse(\"https://console.settlemint.com\").success;\n * // true\n *\n * // Invalid URLs will fail validation\n * const isInvalidUrl = UrlSchema.safeParse(\"not-a-url\").success;\n * // false\n */\nexport const UrlSchema = z.string().url();\nexport type Url = z.infer<typeof UrlSchema>;\n\n/**\n * Schema for validating URL paths.\n *\n * @example\n * import { UrlPathSchema } from \"@settlemint/sdk-utils/validation\";\n *\n * // Validate a URL path\n * const isValidPath = UrlPathSchema.safeParse(\"/api/v1/users\").success;\n * // true\n *\n * // Invalid paths will fail validation\n * const isInvalidPath = UrlPathSchema.safeParse(\"not-a-path\").success;\n * // false\n */\nexport const UrlPathSchema = z.string().regex(/^\\/(?:[a-zA-Z0-9-_]+(?:\\/[a-zA-Z0-9-_]+)*\\/?)?$/, {\n message: \"Invalid URL path format. Must start with '/' and can contain letters, numbers, hyphens, and underscores.\",\n});\n\nexport type UrlPath = z.infer<typeof UrlPathSchema>;\n\n/**\n * Schema that accepts either a full URL or a URL path.\n *\n * @example\n * import { UrlOrPathSchema } from \"@settlemint/sdk-utils/validation\";\n *\n * // Validate a URL\n * const isValidUrl = UrlOrPathSchema.safeParse(\"https://console.settlemint.com\").success;\n * // true\n *\n * // Validate a path\n * const isValidPath = UrlOrPathSchema.safeParse(\"/api/v1/users\").success;\n * // true\n */\nexport const UrlOrPathSchema = z.union([UrlSchema, UrlPathSchema]);\nexport type UrlOrPath = z.infer<typeof UrlOrPathSchema>;\n","import { z } from \"zod\";\nimport { tryParseJson } from \"@/json.js\";\nimport { ApplicationAccessTokenSchema, PersonalAccessTokenSchema } from \"./access-token.schema.js\";\nimport { UniqueNameSchema } from \"./unique-name.schema.js\";\nimport { UrlSchema } from \"./url.schema.js\";\n\n/**\n * Use this value to indicate that the resources are not part of the SettleMint platform.\n */\nexport const STANDALONE_INSTANCE = \"standalone\";\n/**\n * Use this value to indicate that the resources are not part of the SettleMint platform.\n */\nexport const LOCAL_INSTANCE = \"local\";\n\n/**\n * Schema for validating environment variables used by the SettleMint SDK.\n * Defines validation rules and types for configuration values like URLs,\n * access tokens, workspace names, and service endpoints.\n */\nexport const DotEnvSchema = z.object({\n /** Base URL of the SettleMint platform instance, set to standalone if your resources are not part of the SettleMint platform */\n SETTLEMINT_INSTANCE: z\n .union([UrlSchema, z.literal(STANDALONE_INSTANCE), z.literal(LOCAL_INSTANCE)])\n .default(\"https://console.settlemint.com\"),\n /** Application access token for authenticating with SettleMint services */\n SETTLEMINT_ACCESS_TOKEN: ApplicationAccessTokenSchema.optional(),\n /** @internal */\n SETTLEMINT_PERSONAL_ACCESS_TOKEN: PersonalAccessTokenSchema.optional(),\n /** Unique name of the workspace */\n SETTLEMINT_WORKSPACE: UniqueNameSchema.optional(),\n /** Unique name of the application */\n SETTLEMINT_APPLICATION: UniqueNameSchema.optional(),\n /** Unique name of the blockchain network */\n SETTLEMINT_BLOCKCHAIN_NETWORK: UniqueNameSchema.optional(),\n /** Chain ID of the blockchain network */\n SETTLEMINT_BLOCKCHAIN_NETWORK_CHAIN_ID: z.string().optional(),\n /** Unique name of the blockchain node (should have a private key for signing transactions) */\n SETTLEMINT_BLOCKCHAIN_NODE: UniqueNameSchema.optional(),\n /** JSON RPC endpoint for the blockchain node */\n SETTLEMINT_BLOCKCHAIN_NODE_JSON_RPC_ENDPOINT: UrlSchema.optional(),\n /** Unique name of the blockchain node or load balancer */\n SETTLEMINT_BLOCKCHAIN_NODE_OR_LOAD_BALANCER: UniqueNameSchema.optional(),\n /** JSON RPC endpoint for the blockchain node or load balancer */\n SETTLEMINT_BLOCKCHAIN_NODE_OR_LOAD_BALANCER_JSON_RPC_ENDPOINT: UrlSchema.optional(),\n /** Unique name of the Hasura instance */\n SETTLEMINT_HASURA: UniqueNameSchema.optional(),\n /** Endpoint URL for the Hasura GraphQL API */\n SETTLEMINT_HASURA_ENDPOINT: UrlSchema.optional(),\n /** Admin secret for authenticating with Hasura */\n SETTLEMINT_HASURA_ADMIN_SECRET: z.string().optional(),\n /** Database connection URL for Hasura */\n SETTLEMINT_HASURA_DATABASE_URL: z.string().optional(),\n /** Unique name of The Graph instance */\n SETTLEMINT_THEGRAPH: UniqueNameSchema.optional(),\n /** Array of endpoint URLs for The Graph subgraphs */\n SETTLEMINT_THEGRAPH_SUBGRAPHS_ENDPOINTS: z.preprocess(\n (value) => tryParseJson(value as string, []),\n z.array(UrlSchema).optional(),\n ),\n /** Default The Graph subgraph to use */\n SETTLEMINT_THEGRAPH_DEFAULT_SUBGRAPH: z.string().optional(),\n /** Unique name of the Smart Contract Portal instance */\n SETTLEMINT_PORTAL: UniqueNameSchema.optional(),\n /** GraphQL endpoint URL for the Portal */\n SETTLEMINT_PORTAL_GRAPHQL_ENDPOINT: UrlSchema.optional(),\n /** REST endpoint URL for the Portal */\n SETTLEMINT_PORTAL_REST_ENDPOINT: UrlSchema.optional(),\n /** WebSocket endpoint URL for the Portal */\n SETTLEMINT_PORTAL_WS_ENDPOINT: UrlSchema.optional(),\n /** Unique name of the HD private key */\n SETTLEMINT_HD_PRIVATE_KEY: UniqueNameSchema.optional(),\n /** Address of the HD private key forwarder */\n SETTLEMINT_HD_PRIVATE_KEY_FORWARDER_ADDRESS: z.string().optional(),\n /** Unique name of the accessible private key */\n SETTLEMINT_ACCESSIBLE_PRIVATE_KEY: UniqueNameSchema.optional(),\n /** Unique name of the MinIO instance */\n SETTLEMINT_MINIO: UniqueNameSchema.optional(),\n /** Endpoint URL for MinIO */\n SETTLEMINT_MINIO_ENDPOINT: UrlSchema.optional(),\n /** Access key for MinIO authentication */\n SETTLEMINT_MINIO_ACCESS_KEY: z.string().optional(),\n /** Secret key for MinIO authentication */\n SETTLEMINT_MINIO_SECRET_KEY: z.string().optional(),\n /** Unique name of the IPFS instance */\n SETTLEMINT_IPFS: UniqueNameSchema.optional(),\n /** API endpoint URL for IPFS */\n SETTLEMINT_IPFS_API_ENDPOINT: UrlSchema.optional(),\n /** Pinning service endpoint URL for IPFS */\n SETTLEMINT_IPFS_PINNING_ENDPOINT: UrlSchema.optional(),\n /** Gateway endpoint URL for IPFS */\n SETTLEMINT_IPFS_GATEWAY_ENDPOINT: UrlSchema.optional(),\n /** Unique name of the custom deployment */\n SETTLEMINT_CUSTOM_DEPLOYMENT: UniqueNameSchema.optional(),\n /** Endpoint URL for the custom deployment */\n SETTLEMINT_CUSTOM_DEPLOYMENT_ENDPOINT: UrlSchema.optional(),\n /** Unique name of the Blockscout instance */\n SETTLEMINT_BLOCKSCOUT: UniqueNameSchema.optional(),\n /** GraphQL endpoint URL for Blockscout */\n SETTLEMINT_BLOCKSCOUT_GRAPHQL_ENDPOINT: UrlSchema.optional(),\n /** UI endpoint URL for Blockscout */\n SETTLEMINT_BLOCKSCOUT_UI_ENDPOINT: UrlSchema.optional(),\n /** Name of the new project being created */\n SETTLEMINT_NEW_PROJECT_NAME: z.string().optional(),\n /** The log level to use */\n SETTLEMINT_LOG_LEVEL: z.enum([\"debug\", \"info\", \"warn\", \"error\", \"none\"]).default(\"warn\"),\n});\n\n/**\n * Type definition for the environment variables schema.\n */\nexport type DotEnv = z.infer<typeof DotEnvSchema>;\n\n/**\n * Partial version of the environment variables schema where all fields are optional.\n * Useful for validating incomplete configurations during development or build time.\n */\nexport const DotEnvSchemaPartial = DotEnvSchema.partial();\n\n/**\n * Type definition for the partial environment variables schema.\n */\nexport type DotEnvPartial = z.infer<typeof DotEnvSchemaPartial>;\n","import { z } from \"zod\";\n\n/**\n * Schema for validating database IDs. Accepts both PostgreSQL UUIDs and MongoDB ObjectIDs.\n * PostgreSQL UUIDs are 32 hexadecimal characters with hyphens (e.g. 123e4567-e89b-12d3-a456-426614174000).\n * MongoDB ObjectIDs are 24 hexadecimal characters (e.g. 507f1f77bcf86cd799439011).\n *\n * @example\n * import { IdSchema } from \"@settlemint/sdk-utils/validation\";\n *\n * // Validate PostgreSQL UUID\n * const isValidUuid = IdSchema.safeParse(\"123e4567-e89b-12d3-a456-426614174000\").success;\n *\n * // Validate MongoDB ObjectID\n * const isValidObjectId = IdSchema.safeParse(\"507f1f77bcf86cd799439011\").success;\n */\nexport const IdSchema = z.union([\n z\n .string()\n .uuid(), // PostgreSQL UUID\n z\n .string()\n .regex(/^[0-9a-fA-F]{24}$/), // MongoDB ObjectID\n]);\n\n/**\n * Type definition for database IDs, inferred from IdSchema.\n * Can be either a PostgreSQL UUID string or MongoDB ObjectID string.\n */\nexport type Id = z.infer<typeof IdSchema>;\n","import { join } from \"node:path\";\nimport { cancel } from \"@/terminal.js\";\nimport { type DotEnv, type DotEnvPartial, DotEnvSchema, DotEnvSchemaPartial, validate } from \"@/validation.js\";\nimport { config } from \"@dotenvx/dotenvx\";\n\n/**\n * Loads environment variables from .env files.\n * To enable encryption with dotenvx (https://www.dotenvx.com/docs) run `bunx dotenvx encrypt`\n *\n * @param validateEnv - Whether to validate the environment variables against the schema\n * @param prod - Whether to load production environment variables\n * @param path - Optional path to the directory containing .env files. Defaults to process.cwd()\n * @returns A promise that resolves to the validated environment variables\n * @throws Will throw an error if validation fails and validateEnv is true\n * @example\n * import { loadEnv } from '@settlemint/sdk-utils/environment';\n *\n * // Load and validate environment variables\n * const env = await loadEnv(true, false);\n * console.log(env.SETTLEMINT_INSTANCE);\n *\n * // Load without validation\n * const rawEnv = await loadEnv(false, false);\n */\nexport async function loadEnv<T extends boolean = true>(\n validateEnv: T,\n prod: boolean,\n path: string = process.cwd(),\n): Promise<T extends true ? DotEnv : DotEnvPartial> {\n return loadEnvironmentEnv(validateEnv, !!prod, path);\n}\n\nasync function loadEnvironmentEnv<T extends boolean = true>(\n validateEnv: T,\n prod: boolean,\n path: string = process.cwd(),\n): Promise<T extends true ? DotEnv : DotEnvPartial> {\n if (prod) {\n process.env.NODE_ENV = \"production\";\n }\n let { parsed } = config({\n convention: \"nextjs\",\n logLevel: \"error\",\n overload: true,\n quiet: true,\n path: [join(path, \".env\"), join(path, \".env.local\")],\n });\n\n if (!parsed) {\n parsed = {};\n }\n const defaultEnv = Object.fromEntries(\n Object.entries(process.env).filter(([_, value]) => typeof value === \"string\" && value !== \"\"),\n ) as Record<string, string>;\n try {\n return validate(validateEnv ? DotEnvSchema : DotEnvSchemaPartial, {\n ...parsed,\n ...defaultEnv,\n }) as T extends true ? DotEnv : DotEnvPartial;\n } catch (error) {\n cancel((error as Error).message);\n return {} as T extends true ? DotEnv : DotEnvPartial;\n }\n}\n","import { dirname } from \"node:path\";\nimport { findUp } from \"find-up\";\n\n/**\n * Finds the root directory of the current project by locating the nearest package.json file\n *\n * @param fallbackToCwd - If true, will return the current working directory if no package.json is found\n * @param cwd - The directory to start searching for the package.json file from (defaults to process.cwd())\n * @returns Promise that resolves to the absolute path of the project root directory\n * @throws Will throw an error if no package.json is found in the directory tree\n * @example\n * import { projectRoot } from \"@settlemint/sdk-utils/filesystem\";\n *\n * // Get project root path\n * const rootDir = await projectRoot();\n * console.log(`Project root is at: ${rootDir}`);\n */\nexport async function projectRoot(fallbackToCwd = false, cwd?: string): Promise<string> {\n const packageJsonPath = await findUp(\"package.json\", { cwd });\n if (!packageJsonPath) {\n if (fallbackToCwd) {\n return process.cwd();\n }\n throw new Error(\"Unable to find project root (no package.json found)\");\n }\n return dirname(packageJsonPath);\n}\n","import type { PathLike } from \"node:fs\";\nimport { stat } from \"node:fs/promises\";\n\n/**\n * Checks if a file or directory exists at the given path\n *\n * @param path - The file system path to check for existence\n * @returns Promise that resolves to true if the path exists, false otherwise\n * @example\n * import { exists } from \"@settlemint/sdk-utils/filesystem\";\n *\n * // Check if file exists before reading\n * if (await exists('/path/to/file.txt')) {\n * // File exists, safe to read\n * }\n */\nexport async function exists(path: PathLike): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n}\n","import { readFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { findUp } from \"find-up\";\nimport { glob } from \"glob\";\nimport { exists } from \"@/filesystem.js\";\nimport { tryParseJson } from \"@/json.js\";\n\n/**\n * Finds the root directory of a monorepo\n *\n * @param startDir - The directory to start searching from\n * @returns The root directory of the monorepo or null if not found\n * @example\n * import { findMonoRepoRoot } from \"@settlemint/sdk-utils/filesystem\";\n *\n * const root = await findMonoRepoRoot(\"/path/to/your/project\");\n * console.log(root); // Output: /path/to/your/project/packages/core\n */\nexport async function findMonoRepoRoot(startDir: string): Promise<string | null> {\n const lockFilePath = await findUp([\"package-lock.json\", \"yarn.lock\", \"pnpm-lock.yaml\", \"bun.lockb\", \"bun.lock\"], {\n cwd: startDir,\n });\n if (lockFilePath) {\n const packageJsonPath = join(dirname(lockFilePath), \"package.json\");\n const hasWorkSpaces = await packageJsonHasWorkspaces(packageJsonPath);\n return hasWorkSpaces ? dirname(lockFilePath) : null;\n }\n\n let currentDir = startDir;\n\n while (currentDir !== \"/\") {\n const packageJsonPath = join(currentDir, \"package.json\");\n\n if (await packageJsonHasWorkspaces(packageJsonPath)) {\n return currentDir;\n }\n\n const parentDir = dirname(currentDir);\n if (parentDir === currentDir) {\n break; // We've reached the root\n }\n currentDir = parentDir;\n }\n\n return null;\n}\n\n/**\n * Finds all packages in a monorepo\n *\n * @param projectDir - The directory to start searching from\n * @returns An array of package directories\n * @example\n * import { findMonoRepoPackages } from \"@settlemint/sdk-utils/filesystem\";\n *\n * const packages = await findMonoRepoPackages(\"/path/to/your/project\");\n * console.log(packages); // Output: [\"/path/to/your/project/packages/core\", \"/path/to/your/project/packages/ui\"]\n */\nexport async function findMonoRepoPackages(projectDir: string): Promise<string[]> {\n try {\n const monoRepoRoot = await findMonoRepoRoot(projectDir);\n if (!monoRepoRoot) {\n return [projectDir];\n }\n\n const packageJsonPath = join(monoRepoRoot, \"package.json\");\n const packageJson = tryParseJson<{ workspaces: string[] }>(await readFile(packageJsonPath, \"utf-8\"));\n const workspaces = packageJson?.workspaces ?? [];\n\n const packagePaths = await Promise.all(\n workspaces.map(async (workspace: string) => {\n const matches = await glob(join(monoRepoRoot, workspace, \"package.json\"));\n return matches.map((match) => join(match, \"..\"));\n }),\n );\n\n const allPaths = packagePaths.flat();\n // If no packages found in workspaces, treat as non-monorepo\n return allPaths.length === 0 ? [projectDir] : [monoRepoRoot, ...allPaths];\n } catch (_error) {\n // If any error occurs, treat as non-monorepo\n return [projectDir];\n }\n}\n\nasync function packageJsonHasWorkspaces(packageJsonPath: string): Promise<boolean> {\n if (await exists(packageJsonPath)) {\n const packageJson = tryParseJson<{ workspaces: string[] }>(await readFile(packageJsonPath, \"utf-8\"));\n if (packageJson?.workspaces && Array.isArray(packageJson?.workspaces) && packageJson?.workspaces.length > 0) {\n return true;\n }\n }\n return false;\n}\n","import { writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { exists, projectRoot } from \"@/filesystem.js\";\nimport type { DotEnv } from \"@/validation.js\";\nimport { DotEnvSchema } from \"@/validation/dot-env.schema.js\";\nimport { config } from \"@dotenvx/dotenvx\";\nimport { deepmerge } from \"deepmerge-ts\";\nimport { findMonoRepoPackages } from \"../filesystem/mono-repo.js\";\n\n/**\n * Writes environment variables to .env files across a project or monorepo\n *\n * @param options - The options for writing the environment variables\n * @param options.prod - Whether to write production environment variables\n * @param options.env - The environment variables to write\n * @param options.secrets - Whether to write to .env.local files for secrets\n * @param options.cwd - The directory to start searching for the package.json file from (defaults to process.cwd())\n * @returns Promise that resolves when writing is complete\n * @throws Will throw an error if writing fails\n * @example\n * import { writeEnv } from '@settlemint/sdk-utils/environment';\n *\n * // Write development environment variables\n * await writeEnv({\n * prod: false,\n * env: {\n * SETTLEMINT_INSTANCE: 'https://dev.example.com'\n * },\n * secrets: false\n * });\n *\n * // Write production secrets\n * await writeEnv({\n * prod: true,\n * env: {\n * SETTLEMINT_ACCESS_TOKEN: 'secret-token'\n * },\n * secrets: true\n * });\n */\nexport async function writeEnv({\n prod,\n env,\n secrets,\n cwd,\n}: {\n prod: boolean;\n env: Partial<DotEnv>;\n secrets: boolean;\n cwd?: string;\n}): Promise<void> {\n const projectDir = await projectRoot(true, cwd);\n\n if (prod) {\n process.env.NODE_ENV = \"production\";\n }\n\n const targetDirs = await findMonoRepoPackages(projectDir);\n\n await Promise.all(\n targetDirs.map(async (dir) => {\n const envFile = join(\n dir,\n secrets ? `.env${prod ? \".production\" : \"\"}.local` : `.env${prod ? \".production\" : \"\"}`,\n );\n\n let { parsed: currentEnv } = (await exists(envFile))\n ? config({\n path: envFile,\n logLevel: \"error\",\n quiet: true,\n })\n : { parsed: {} };\n\n if (!currentEnv) {\n currentEnv = {};\n }\n\n const prunedEnv = pruneCurrentEnv(currentEnv, env);\n const mergedEnv = deepmerge(prunedEnv, env as Record<string, unknown>);\n await writeFile(envFile, stringify(mergedEnv));\n }),\n );\n}\n\nconst quote = /[\\s\"'#]/;\n\nfunction stringifyPair([key, val]: [string, unknown]): string | undefined {\n if (val === undefined) {\n return undefined;\n }\n if (val === null) {\n return `${key}=\"\"`;\n }\n\n const type = typeof val;\n if (type === \"string\") {\n return `${key}=${quote.test(val as string) ? JSON.stringify(val) : val}`;\n }\n if (type === \"boolean\" || type === \"number\") {\n return `${key}=${val}`;\n }\n if (type === \"object\") {\n return `${key}=${JSON.stringify(val)}`;\n }\n\n throw new Error(`Unsupported type for key \"${key}\": ${type}`);\n}\n\nfunction stringify(obj: Record<string, unknown>): string {\n return Object.entries(obj)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(stringifyPair)\n .filter((value) => value !== undefined)\n .join(\"\\n\");\n}\n\n/**\n * Prunes the current environment variables from the new environment variables\n *\n * @param currentEnv - The current environment variables\n * @param env - The new environment variables\n * @returns The new environment variables with the current environment variables removed\n */\nfunction pruneCurrentEnv(currentEnv: Record<string, unknown>, env: Partial<DotEnv>): Record<string, unknown> {\n const dotEnvKeys = Object.keys(DotEnvSchema.shape);\n return Object.fromEntries(\n Object.entries(currentEnv).filter(([key]) => {\n if (dotEnvKeys.includes(key) && !env[key as keyof typeof env]) {\n return false;\n }\n return true;\n }),\n );\n}\n"],"mappings":"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"}
@@ -13,10 +13,18 @@ import { deepmerge } from "deepmerge-ts";
13
13
 
14
14
  //#region src/terminal/should-print.ts
15
15
  /**
16
- * Returns true if the terminal should print, false otherwise.
17
- * When CLAUDECODE, REPL_ID, or AGENT env vars are set, suppresses info/debug output
18
- * but warnings and errors will still be displayed.
19
- * @returns true if the terminal should print, false otherwise.
16
+ * Determines whether terminal output should be printed based on environment variables.
17
+ *
18
+ * **Environment Variable Precedence:**
19
+ * 1. `SETTLEMINT_DISABLE_TERMINAL="true"` - Completely disables all terminal output (highest priority)
20
+ * 2. `CLAUDECODE`, `REPL_ID`, or `AGENT` (any truthy value) - Enables quiet mode, suppressing info/debug/status messages
21
+ *
22
+ * **Quiet Mode Behavior:**
23
+ * When quiet mode is active (Claude Code environments), this function returns `false` to suppress
24
+ * informational output. However, warnings and errors are always displayed regardless of quiet mode,
25
+ * as they are handled separately in the `note()` function with level-based filtering.
26
+ *
27
+ * @returns `true` if terminal output should be printed, `false` if suppressed
20
28
  */
21
29
  function shouldPrint() {
22
30
  if (process.env.SETTLEMINT_DISABLE_TERMINAL === "true") {
@@ -228,13 +236,62 @@ const intro = (msg) => {
228
236
  //#endregion
229
237
  //#region src/terminal/note.ts
230
238
  /**
239
+ * Applies color to a message if not already colored.
240
+ * @param msg - The message to colorize
241
+ * @param level - The severity level determining the color
242
+ * @returns Colorized message (yellow for warnings, red for errors, unchanged for info)
243
+ */
244
+ function colorize(msg, level) {
245
+ if (msg.includes("\x1B[")) {
246
+ return msg;
247
+ }
248
+ if (level === "warn") {
249
+ return yellowBright(msg);
250
+ }
251
+ if (level === "error") {
252
+ return redBright(msg);
253
+ }
254
+ return msg;
255
+ }
256
+ /**
257
+ * Determines whether a message should be printed based on its level and quiet mode.
258
+ * @param level - The severity level of the message
259
+ * @returns true if the message should be printed, false otherwise
260
+ */
261
+ function canPrint(level) {
262
+ if (level !== "info") {
263
+ return true;
264
+ }
265
+ return shouldPrint();
266
+ }
267
+ /**
268
+ * Prepares a message for display by converting Error objects and masking tokens.
269
+ * @param value - The message string or Error object
270
+ * @param level - The severity level (stack traces are included for errors)
271
+ * @returns Masked message text, optionally with stack trace
272
+ */
273
+ function prepareMessage(value, level) {
274
+ let text;
275
+ if (value instanceof Error) {
276
+ text = value.message;
277
+ if (level === "error" && value.stack) {
278
+ text = `${text}\n\n${value.stack}`;
279
+ }
280
+ } else {
281
+ text = value;
282
+ }
283
+ return maskTokens(text);
284
+ }
285
+ /**
231
286
  * Displays a note message with optional warning or error level formatting.
232
287
  * Regular notes are displayed in normal text, warnings are shown in yellow, and errors in red.
233
288
  * Any sensitive tokens in the message are masked before display.
234
289
  * Warnings and errors are always displayed, even in quiet mode (when CLAUDECODE, REPL_ID, or AGENT env vars are set).
235
290
  * When an Error object is provided with level "error", the stack trace is automatically included.
236
291
  *
237
- * @param message - The message to display as a note, or an Error object
292
+ * @param message - The message to display as a note. Can be either:
293
+ * - A string: Displayed directly with appropriate styling
294
+ * - An Error object: The error message is displayed, and for level "error", the stack trace is automatically included
238
295
  * @param level - The note level: "info" (default), "warn" for warning styling, or "error" for error styling
239
296
  * @example
240
297
  * import { note } from "@settlemint/sdk-utils/terminal";
@@ -245,46 +302,30 @@ const intro = (msg) => {
245
302
  * // Display warning note
246
303
  * note("Low disk space remaining", "warn");
247
304
  *
248
- * // Display error note
305
+ * // Display error note (string)
249
306
  * note("Operation failed", "error");
250
307
  *
251
- * // Display error with stack trace automatically
308
+ * // Display error with stack trace automatically (Error object)
252
309
  * try {
253
310
  * // some operation
254
311
  * } catch (error) {
312
+ * // If error is an Error object and level is "error", stack trace is included automatically
255
313
  * note(error, "error");
256
314
  * }
257
315
  */
258
316
  const note = (message, level = "info") => {
259
- let messageText;
260
- let _error;
261
- if (message instanceof Error) {
262
- _error = message;
263
- messageText = message.message;
264
- if (level === "error" && message.stack) {
265
- messageText = `${messageText}\n\n${message.stack}`;
266
- }
267
- } else {
268
- messageText = message;
269
- }
270
- const maskedMessage = maskTokens(messageText);
271
- const _isQuietMode = process.env.CLAUDECODE || process.env.REPL_ID || process.env.AGENT;
272
- if (level === "warn" || level === "error") {
273
- console.log("");
274
- if (level === "warn") {
275
- const coloredMessage = maskedMessage.includes("\x1B[") ? maskedMessage : yellowBright(maskedMessage);
276
- console.warn(coloredMessage);
277
- } else {
278
- const coloredMessage = maskedMessage.includes("\x1B[") ? maskedMessage : redBright(maskedMessage);
279
- console.error(coloredMessage);
280
- }
281
- return;
282
- }
283
- if (!shouldPrint()) {
317
+ if (!canPrint(level)) {
284
318
  return;
285
319
  }
320
+ const msg = prepareMessage(message, level);
286
321
  console.log("");
287
- console.log(maskedMessage);
322
+ if (level === "warn") {
323
+ console.warn(colorize(msg, level));
324
+ } else if (level === "error") {
325
+ console.error(colorize(msg, level));
326
+ } else {
327
+ console.log(msg);
328
+ }
288
329
  };
289
330
 
290
331
  //#endregion
@@ -380,8 +421,7 @@ var SpinnerError = class extends Error {
380
421
  const spinner = async (options) => {
381
422
  const handleError = (error) => {
382
423
  note(error, "error");
383
- const errorMessage = maskTokens(error.message);
384
- throw new SpinnerError(errorMessage, error);
424
+ throw new SpinnerError(error.message, error);
385
425
  };
386
426
  if (isInCi || !shouldPrint()) {
387
427
  try {