@settlemint/sdk-utils
Version:
Shared utilities and helper functions for SettleMint SDK modules
940 lines (917 loc) • 28.9 kB
JavaScript
import { readFile, readdir, rm, stat } from "node:fs/promises";
import { dirname, join, resolve } from "node:path";
import { findUp } from "find-up";
import { glob } from "glob";
import { detect } from "package-manager-detector/detect";
import { installPackage } from "@antfu/install-pkg";
import { greenBright, inverse, magentaBright, redBright, whiteBright, yellowBright } from "yoctocolors";
import { spawn } from "node:child_process";
import isInCi from "is-in-ci";
import yoctoSpinner from "yocto-spinner";
import { Table } from "console-table-printer";
import pkgjs from "@npmcli/package-json";
//#region src/filesystem/project-root.ts
/**
* Finds the root directory of the current project by locating the nearest package.json file
*
* @param fallbackToCwd - If true, will return the current working directory if no package.json is found
* @param cwd - The directory to start searching for the package.json file from (defaults to process.cwd())
* @returns Promise that resolves to the absolute path of the project root directory
* @throws Will throw an error if no package.json is found in the directory tree
* @example
* import { projectRoot } from "@settlemint/sdk-utils/filesystem";
*
* // Get project root path
* const rootDir = await projectRoot();
* console.log(`Project root is at: ${rootDir}`);
*/
async function projectRoot(fallbackToCwd = false, cwd) {
const packageJsonPath = await findUp("package.json", { cwd });
if (!packageJsonPath) {
if (fallbackToCwd) {
return process.cwd();
}
throw new Error("Unable to find project root (no package.json found)");
}
return dirname(packageJsonPath);
}
//#endregion
//#region src/filesystem/exists.ts
/**
* Checks if a file or directory exists at the given path
*
* @param path - The file system path to check for existence
* @returns Promise that resolves to true if the path exists, false otherwise
* @example
* import { exists } from "@settlemint/sdk-utils/filesystem";
*
* // Check if file exists before reading
* if (await exists('/path/to/file.txt')) {
* // File exists, safe to read
* }
*/
async function exists(path) {
try {
await stat(path);
return true;
} catch {
return false;
}
}
//#endregion
//#region src/json.ts
/**
* Attempts to parse a JSON string into a typed value, returning a default value if parsing fails.
*
* @param value - The JSON string to parse
* @param defaultValue - The value to return if parsing fails or results in null/undefined
* @returns The parsed JSON value as type T, or the default value if parsing fails
*
* @example
* import { tryParseJson } from "@settlemint/sdk-utils";
*
* const config = tryParseJson<{ port: number }>(
* '{"port": 3000}',
* { port: 8080 }
* );
* // Returns: { port: 3000 }
*
* const invalid = tryParseJson<string[]>(
* 'invalid json',
* []
* );
* // Returns: []
*/
function tryParseJson(value, defaultValue = null) {
try {
const parsed = JSON.parse(value);
if (parsed === undefined || parsed === null) {
return defaultValue;
}
return parsed;
} catch (_err) {
return defaultValue;
}
}
/**
* Extracts a JSON object from a string.
*
* @param value - The string to extract the JSON object from
* @returns The parsed JSON object, or null if no JSON object is found
* @throws {Error} If the input string is too long (longer than 5000 characters)
* @example
* import { extractJsonObject } from "@settlemint/sdk-utils";
*
* const json = extractJsonObject<{ port: number }>(
* 'port info: {"port": 3000}',
* );
* // Returns: { port: 3000 }
*/
function extractJsonObject(value) {
if (value.length > 5e3) {
throw new Error("Input too long");
}
const result = /\{([\s\S]*)\}/.exec(value);
if (!result) {
return null;
}
return tryParseJson(result[0]);
}
/**
* Converts a value to a JSON stringifiable format.
*
* @param value - The value to convert
* @returns The JSON stringifiable value
*
* @example
* import { makeJsonStringifiable } from "@settlemint/sdk-utils";
*
* const json = makeJsonStringifiable<{ amount: bigint }>({ amount: BigInt(1000) });
* // Returns: '{"amount":"1000"}'
*/
function makeJsonStringifiable(value) {
if (value === undefined || value === null) {
return value;
}
return tryParseJson(JSON.stringify(value, (_, value$1) => typeof value$1 === "bigint" ? value$1.toString() : value$1));
}
//#endregion
//#region src/filesystem/mono-repo.ts
/**
* Finds the root directory of a monorepo
*
* @param startDir - The directory to start searching from
* @returns The root directory of the monorepo or null if not found
* @example
* import { findMonoRepoRoot } from "@settlemint/sdk-utils/filesystem";
*
* const root = await findMonoRepoRoot("/path/to/your/project");
* console.log(root); // Output: /path/to/your/project/packages/core
*/
async function findMonoRepoRoot(startDir) {
const lockFilePath = await findUp([
"package-lock.json",
"yarn.lock",
"pnpm-lock.yaml",
"bun.lockb",
"bun.lock"
], { cwd: startDir });
if (lockFilePath) {
const packageJsonPath = join(dirname(lockFilePath), "package.json");
const hasWorkSpaces = await packageJsonHasWorkspaces(packageJsonPath);
return hasWorkSpaces ? dirname(lockFilePath) : null;
}
let currentDir = startDir;
while (currentDir !== "/") {
const packageJsonPath = join(currentDir, "package.json");
if (await packageJsonHasWorkspaces(packageJsonPath)) {
return currentDir;
}
const parentDir = dirname(currentDir);
if (parentDir === currentDir) {
break;
}
currentDir = parentDir;
}
return null;
}
/**
* Finds all packages in a monorepo
*
* @param projectDir - The directory to start searching from
* @returns An array of package directories
* @example
* import { findMonoRepoPackages } from "@settlemint/sdk-utils/filesystem";
*
* const packages = await findMonoRepoPackages("/path/to/your/project");
* console.log(packages); // Output: ["/path/to/your/project/packages/core", "/path/to/your/project/packages/ui"]
*/
async function findMonoRepoPackages(projectDir) {
try {
const monoRepoRoot = await findMonoRepoRoot(projectDir);
if (!monoRepoRoot) {
return [projectDir];
}
const packageJsonPath = join(monoRepoRoot, "package.json");
const packageJson = tryParseJson(await readFile(packageJsonPath, "utf-8"));
const workspaces = packageJson?.workspaces ?? [];
const packagePaths = await Promise.all(workspaces.map(async (workspace) => {
const matches = await glob(join(monoRepoRoot, workspace, "package.json"));
return matches.map((match) => join(match, ".."));
}));
const allPaths = packagePaths.flat();
return allPaths.length === 0 ? [projectDir] : [monoRepoRoot, ...allPaths];
} catch (_error) {
return [projectDir];
}
}
async function packageJsonHasWorkspaces(packageJsonPath) {
if (await exists(packageJsonPath)) {
const packageJson = tryParseJson(await readFile(packageJsonPath, "utf-8"));
if (packageJson?.workspaces && Array.isArray(packageJson?.workspaces) && packageJson?.workspaces.length > 0) {
return true;
}
}
return false;
}
//#endregion
//#region src/package-manager/download-and-extract.ts
/**
* Formats a directory path by removing trailing slashes and whitespace
*
* @param targetDir - The directory path to format
* @returns The formatted directory path
* @example
* import { formatTargetDir } from "@settlemint/sdk-utils/package-manager";
*
* const formatted = formatTargetDir("/path/to/dir/ "); // "/path/to/dir"
*/
function formatTargetDir(targetDir) {
return targetDir?.trim().replace(/\/+$/g, "");
}
/**
* Checks if a directory is empty or contains only a .git folder
*
* @param path - The directory path to check
* @returns True if directory is empty or contains only .git, false otherwise
* @example
* import { isEmpty } from "@settlemint/sdk-utils/package-manager";
*
* if (await isEmpty("/path/to/dir")) {
* // Directory is empty
* }
*/
async function isEmpty(path) {
const files = await readdir(path);
return files.length === 0 || files.length === 1 && files[0] === ".git";
}
/**
* Removes all contents of a directory except the .git folder
*
* @param dir - The directory path to empty
* @example
* import { emptyDir } from "@settlemint/sdk-utils/package-manager";
*
* await emptyDir("/path/to/dir"); // Removes all contents except .git
*/
async function emptyDir(dir) {
if (!await exists(dir)) return;
for (const file of await readdir(dir)) {
if (file === ".git") continue;
await rm(resolve(dir, file), {
recursive: true,
force: true
});
}
}
//#endregion
//#region src/package-manager/get-package-manager.ts
/**
* Detects the package manager used in the current project
*
* @param targetDir - The directory to check for package manager (optional, defaults to process.cwd())
* @returns The name of the package manager
* @example
* import { getPackageManager } from "@settlemint/sdk-utils/package-manager";
*
* const packageManager = await getPackageManager();
* console.log(`Using ${packageManager}`);
*/
async function getPackageManager(targetDir) {
const packageManager = await detect({ cwd: targetDir || process.cwd() });
return packageManager?.name ?? "npm";
}
//#endregion
//#region src/package-manager/get-package-manager-executable.ts
/**
* Retrieves the executable command and arguments for the package manager
*
* @param targetDir - The directory to check for package manager (optional, defaults to process.cwd())
* @returns An object containing the command and arguments for the package manager
* @example
* import { getPackageManagerExecutable } from "@settlemint/sdk-utils/package-manager";
*
* const { command, args } = await getPackageManagerExecutable();
* console.log(`Using ${command} with args: ${args.join(" ")}`);
*/
async function getPackageManagerExecutable(targetDir) {
const packageManager = await getPackageManager(targetDir ?? process.cwd());
switch (packageManager) {
case "pnpm": return {
command: "pnpm",
args: ["dlx"]
};
case "bun": return {
command: "bunx",
args: []
};
case "yarn": return {
command: "yarn",
args: ["create"]
};
}
return {
command: "npx",
args: []
};
}
//#endregion
//#region src/terminal/should-print.ts
/**
* Determines whether terminal output should be printed based on environment variables.
*
* **Environment Variable Precedence:**
* 1. `SETTLEMINT_DISABLE_TERMINAL="true"` - Completely disables all terminal output (highest priority)
* 2. `CLAUDECODE`, `REPL_ID`, or `AGENT` (any truthy value) - Enables quiet mode, suppressing info/debug/status messages
*
* **Quiet Mode Behavior:**
* When quiet mode is active (Claude Code environments), this function returns `false` to suppress
* informational output. However, warnings and errors are always displayed regardless of quiet mode,
* as they are handled separately in the `note()` function with level-based filtering.
*
* @returns `true` if terminal output should be printed, `false` if suppressed
*/
function shouldPrint() {
if (process.env.SETTLEMINT_DISABLE_TERMINAL === "true") {
return false;
}
if (process.env.CLAUDECODE || process.env.REPL_ID || process.env.AGENT) {
return false;
}
return true;
}
//#endregion
//#region src/terminal/ascii.ts
/**
* Prints the SettleMint ASCII art logo to the console in magenta color.
* Used for CLI branding and visual identification.
*
* @example
* import { ascii } from "@settlemint/sdk-utils/terminal";
*
* // Prints the SettleMint logo
* ascii();
*/
const ascii = () => {
if (!shouldPrint()) {
return;
}
console.log(magentaBright(`
_________ __ __ .__ _____ .__ __
/ _____/ _____/ |__/ |_| | ____ / \\ |__| _____/ |_
\\_____ \\_/ __ \\ __\\ __\\ | _/ __ \\ / \\ / \\| |/ \\ __\\
/ \\ ___/| | | | | |_\\ ___// Y \\ | | \\ |
/_________/\\_____>__| |__| |____/\\_____>____|____/__|___|__/__|
`));
};
//#endregion
//#region src/logging/mask-tokens.ts
/**
* Masks sensitive SettleMint tokens in output text by replacing them with asterisks.
* Handles personal access tokens (PAT), application access tokens (AAT), and service account tokens (SAT).
*
* @param output - The text string that may contain sensitive tokens
* @returns The text with any sensitive tokens masked with asterisks
* @example
* import { maskTokens } from "@settlemint/sdk-utils/terminal";
*
* // Masks a token in text
* const masked = maskTokens("Token: sm_pat_****"); // "Token: ***"
*/
const maskTokens = (output) => {
return output.replace(/sm_(pat|aat|sat)_[0-9a-zA-Z]+/g, "***");
};
//#endregion
//#region src/terminal/cancel.ts
/**
* Error class used to indicate that the operation was cancelled.
* This error is used to signal that the operation should be aborted.
*/
var CancelError = class extends Error {};
/**
* Displays an error message in red inverse text and throws a CancelError.
* Used to terminate execution with a visible error message.
* Any sensitive tokens in the message are masked before display.
*
* @param msg - The error message to display
* @returns never - Function does not return as it throws an error
* @example
* import { cancel } from "@settlemint/sdk-utils/terminal";
*
* // Exits process with error message
* cancel("An error occurred");
*/
const cancel = (msg) => {
console.log("");
console.log(inverse(redBright(maskTokens(msg))));
console.log("");
throw new CancelError(msg);
};
//#endregion
//#region src/terminal/execute-command.ts
/**
* Error class for command execution errors
* @extends Error
*/
var CommandError = class extends Error {
/**
* Constructs a new CommandError
* @param message - The error message
* @param code - The exit code of the command
* @param output - The output of the command
*/
constructor(message, code, output) {
super(message);
this.code = code;
this.output = output;
}
};
/**
* Checks if we're in quiet mode (Claude Code environment)
*/
function isQuietMode() {
return !!(process.env.CLAUDECODE || process.env.REPL_ID || process.env.AGENT);
}
/**
* Executes a command with the given arguments in a child process.
* Pipes stdin to the child process and captures stdout/stderr output.
* Masks any sensitive tokens in the output before displaying or returning.
* In quiet mode (when CLAUDECODE, REPL_ID, or AGENT env vars are set),
* output is suppressed unless the command errors out.
*
* @param command - The command to execute
* @param args - Array of arguments to pass to the command
* @param options - Options for customizing command execution
* @returns Array of output strings from stdout and stderr
* @throws {CommandError} If the process fails to start or exits with non-zero code
* @example
* import { executeCommand } from "@settlemint/sdk-utils/terminal";
*
* // Execute git clone
* await executeCommand("git", ["clone", "repo-url"]);
*
* // Execute silently
* await executeCommand("npm", ["install"], { silent: true });
*/
async function executeCommand(command, args, options) {
const { silent,...spawnOptions } = options ?? {};
const quietMode = isQuietMode();
const shouldSuppressOutput = quietMode ? silent !== false : !!silent;
const child = spawn(command, args, {
...spawnOptions,
env: {
...process.env,
...options?.env
}
});
process.stdin.pipe(child.stdin);
const output = [];
const stdoutOutput = [];
const stderrOutput = [];
return new Promise((resolve$1, reject) => {
child.stdout.on("data", (data) => {
const maskedData = maskTokens(data.toString());
if (!shouldSuppressOutput) {
process.stdout.write(maskedData);
}
output.push(maskedData);
stdoutOutput.push(maskedData);
});
child.stderr.on("data", (data) => {
const maskedData = maskTokens(data.toString());
if (!shouldSuppressOutput) {
process.stderr.write(maskedData);
}
output.push(maskedData);
stderrOutput.push(maskedData);
});
const showErrorOutput = () => {
if (quietMode && shouldSuppressOutput && output.length > 0) {
if (stdoutOutput.length > 0) {
process.stdout.write(stdoutOutput.join(""));
}
if (stderrOutput.length > 0) {
process.stderr.write(stderrOutput.join(""));
}
}
};
child.on("error", (err) => {
process.stdin.unpipe(child.stdin);
showErrorOutput();
reject(new CommandError(err.message, "code" in err && typeof err.code === "number" ? err.code : 1, output));
});
child.on("close", (code) => {
process.stdin.unpipe(child.stdin);
if (code === 0 || code === null || code === 143) {
resolve$1(output);
return;
}
showErrorOutput();
reject(new CommandError(`Command "${command}" exited with code ${code}`, code, output));
});
});
}
//#endregion
//#region src/terminal/intro.ts
/**
* Displays an introductory message in magenta text with padding.
* Any sensitive tokens in the message are masked before display.
*
* @param msg - The message to display as introduction
* @example
* import { intro } from "@settlemint/sdk-utils/terminal";
*
* // Display intro message
* intro("Starting deployment...");
*/
const intro = (msg) => {
if (!shouldPrint()) {
return;
}
console.log("");
console.log(magentaBright(maskTokens(msg)));
console.log("");
};
//#endregion
//#region src/terminal/note.ts
/**
* Applies color to a message if not already colored.
* @param msg - The message to colorize
* @param level - The severity level determining the color
* @returns Colorized message (yellow for warnings, red for errors, unchanged for info)
*/
function colorize(msg, level) {
if (msg.includes("\x1B[")) {
return msg;
}
if (level === "warn") {
return yellowBright(msg);
}
if (level === "error") {
return redBright(msg);
}
return msg;
}
/**
* Determines whether a message should be printed based on its level and quiet mode.
* @param level - The severity level of the message
* @returns true if the message should be printed, false otherwise
*/
function canPrint(level) {
if (level !== "info") {
return true;
}
return shouldPrint();
}
/**
* Prepares a message for display by converting Error objects and masking tokens.
* @param value - The message string or Error object
* @param level - The severity level (stack traces are included for errors)
* @returns Masked message text, optionally with stack trace
*/
function prepareMessage(value, level) {
let text;
if (value instanceof Error) {
text = value.message;
if (level === "error" && value.stack) {
text = `${text}\n\n${value.stack}`;
}
} else {
text = value;
}
return maskTokens(text);
}
/**
* Displays a note message with optional warning or error level formatting.
* Regular notes are displayed in normal text, warnings are shown in yellow, and errors in red.
* Any sensitive tokens in the message are masked before display.
* Warnings and errors are always displayed, even in quiet mode (when CLAUDECODE, REPL_ID, or AGENT env vars are set).
* When an Error object is provided with level "error", the stack trace is automatically included.
*
* @param message - The message to display as a note. Can be either:
* - A string: Displayed directly with appropriate styling
* - An Error object: The error message is displayed, and for level "error", the stack trace is automatically included
* @param level - The note level: "info" (default), "warn" for warning styling, or "error" for error styling
* @example
* import { note } from "@settlemint/sdk-utils/terminal";
*
* // Display info note
* note("Operation completed successfully");
*
* // Display warning note
* note("Low disk space remaining", "warn");
*
* // Display error note (string)
* note("Operation failed", "error");
*
* // Display error with stack trace automatically (Error object)
* try {
* // some operation
* } catch (error) {
* // If error is an Error object and level is "error", stack trace is included automatically
* note(error, "error");
* }
*/
const note = (message, level = "info") => {
if (!canPrint(level)) {
return;
}
const msg = prepareMessage(message, level);
console.log("");
if (level === "warn") {
console.warn(colorize(msg, level));
} else if (level === "error") {
console.error(colorize(msg, level));
} else {
console.log(msg);
}
};
//#endregion
//#region src/terminal/list.ts
/**
* Displays a list of items in a formatted manner, supporting nested items.
*
* @param title - The title of the list
* @param items - The items to display, can be strings or arrays for nested items
* @returns The formatted list
* @example
* import { list } from "@settlemint/sdk-utils/terminal";
*
* // Simple list
* list("Use cases", ["use case 1", "use case 2", "use case 3"]);
*
* // Nested list
* list("Providers", [
* "AWS",
* ["us-east-1", "eu-west-1"],
* "Azure",
* ["eastus", "westeurope"]
* ]);
*/
function list(title, items) {
const formatItems = (items$1) => {
return items$1.map((item) => {
if (Array.isArray(item)) {
return item.map((subItem) => ` • ${subItem}`).join("\n");
}
return ` • ${item}`;
}).join("\n");
};
return note(`${title}:\n\n${formatItems(items)}`);
}
//#endregion
//#region src/terminal/outro.ts
/**
* Displays a closing message in green inverted text with padding.
* Any sensitive tokens in the message are masked before display.
*
* @param msg - The message to display as conclusion
* @example
* import { outro } from "@settlemint/sdk-utils/terminal";
*
* // Display outro message
* outro("Deployment completed successfully!");
*/
const outro = (msg) => {
if (!shouldPrint()) {
return;
}
console.log("");
console.log(inverse(greenBright(maskTokens(msg))));
console.log("");
};
//#endregion
//#region src/terminal/spinner.ts
/**
* Error class used to indicate that the spinner operation failed.
* This error is used to signal that the operation should be aborted.
*/
var SpinnerError = class extends Error {
constructor(message, originalError) {
super(message);
this.originalError = originalError;
this.name = "SpinnerError";
}
};
/**
* Displays a loading spinner while executing an async task.
* Shows progress with start/stop messages and handles errors.
* Spinner is disabled in CI environments.
*
* @param options - Configuration options for the spinner
* @returns The result from the executed task
* @throws Will exit process with code 1 if task fails
* @example
* import { spinner } from "@settlemint/sdk-utils/terminal";
*
* // Show spinner during async task
* const result = await spinner({
* startMessage: "Deploying...",
* task: async () => {
* // Async work here
* return "success";
* },
* stopMessage: "Deployed successfully!"
* });
*/
const spinner = async (options) => {
const handleError = (error) => {
note(error, "error");
throw new SpinnerError(error.message, error);
};
if (isInCi || !shouldPrint()) {
try {
return await options.task();
} catch (err) {
return handleError(err);
}
}
const spinner$1 = yoctoSpinner({ stream: process.stdout }).start(options.startMessage);
try {
const result = await options.task(spinner$1);
spinner$1.success(options.stopMessage);
await new Promise((resolve$1) => process.nextTick(resolve$1));
return result;
} catch (err) {
spinner$1.error(redBright(`${options.startMessage} --> Error!`));
return handleError(err);
}
};
//#endregion
//#region src/string.ts
/**
* Capitalizes the first letter of a string.
*
* @param val - The string to capitalize
* @returns The input string with its first letter capitalized
*
* @example
* import { capitalizeFirstLetter } from "@settlemint/sdk-utils";
*
* const capitalized = capitalizeFirstLetter("hello");
* // Returns: "Hello"
*/
function capitalizeFirstLetter(val) {
return String(val).charAt(0).toUpperCase() + String(val).slice(1);
}
/**
* Converts a camelCase string to a human-readable string.
*
* @param s - The camelCase string to convert
* @returns The human-readable string
*
* @example
* import { camelCaseToWords } from "@settlemint/sdk-utils";
*
* const words = camelCaseToWords("camelCaseString");
* // Returns: "Camel Case String"
*/
function camelCaseToWords(s) {
const result = s.replace(/([a-z])([A-Z])/g, "$1 $2");
const withSpaces = result.replace(/([A-Z])([a-z])/g, " $1$2");
const capitalized = capitalizeFirstLetter(withSpaces);
return capitalized.replace(/\s+/g, " ").trim();
}
/**
* Replaces underscores and hyphens with spaces.
*
* @param s - The string to replace underscores and hyphens with spaces
* @returns The input string with underscores and hyphens replaced with spaces
*
* @example
* import { replaceUnderscoresAndHyphensWithSpaces } from "@settlemint/sdk-utils";
*
* const result = replaceUnderscoresAndHyphensWithSpaces("Already_Spaced-Second");
* // Returns: "Already Spaced Second"
*/
function replaceUnderscoresAndHyphensWithSpaces(s) {
return s.replace(/[-_]/g, " ");
}
/**
* Truncates a string to a maximum length and appends "..." if it is longer.
*
* @param value - The string to truncate
* @param maxLength - The maximum length of the string
* @returns The truncated string or the original string if it is shorter than the maximum length
*
* @example
* import { truncate } from "@settlemint/sdk-utils";
*
* const truncated = truncate("Hello, world!", 10);
* // Returns: "Hello, wor..."
*/
function truncate(value, maxLength) {
if (value.length <= maxLength) {
return value;
}
return `${value.slice(0, maxLength)}...`;
}
//#endregion
//#region src/terminal/table.ts
/**
* Displays data in a formatted table in the terminal.
*
* @param title - Title to display above the table
* @param data - Array of objects to display in table format
* @example
* import { table } from "@settlemint/sdk-utils/terminal";
*
* const data = [
* { name: "Item 1", value: 100 },
* { name: "Item 2", value: 200 }
* ];
*
* table("My Table", data);
*/
function table(title, data) {
if (!shouldPrint()) {
return;
}
note(title);
if (!data || data.length === 0) {
note("No data to display");
return;
}
const columnKeys = Object.keys(data[0]);
const table$1 = new Table({ columns: columnKeys.map((key) => ({
name: key,
title: whiteBright(camelCaseToWords(key)),
alignment: "left"
})) });
table$1.addRows(data);
table$1.printTable();
}
//#endregion
//#region src/package-manager/install-dependencies.ts
/**
* Installs one or more packages as dependencies using the detected package manager
*
* @param pkgs - A single package name or array of package names to install
* @param cwd - The directory to run the installation in
* @returns A promise that resolves when installation is complete
* @throws If package installation fails
* @example
* import { installDependencies } from "@settlemint/sdk-utils/package-manager";
*
* // Install a single package
* await installDependencies("express");
*
* // Install multiple packages
* await installDependencies(["express", "cors"]);
*/
async function installDependencies(pkgs, cwd) {
try {
await installPackage(pkgs, {
silent: true,
additionalArgs: ["--exact"],
cwd
});
} catch (err) {
const error = err instanceof Error ? err.message : "Unknown error";
note(`Failed to install ${Array.isArray(pkgs) ? `dependencies '${pkgs.join(", ")}'` : `dependency '${pkgs}'`}: ${error}`, "warn");
}
}
//#endregion
//#region src/package-manager/is-package-installed.ts
/**
* Checks if a package is installed in the project's dependencies, devDependencies, or peerDependencies.
*
* @param name - The name of the package to check
* @param path - The path to the project root directory. If not provided, will be automatically determined
* @returns Whether the package is installed
* @throws If unable to read or parse the package.json file
* @example
* import { isPackageInstalled } from "@settlemint/sdk-utils/package-manager";
*
* const isInstalled = await isPackageInstalled("@settlemint/sdk-utils");
* console.log(`@settlemint/sdk-utils is installed: ${isInstalled}`);
*/
async function isPackageInstalled(name, path) {
const pkgJson = await pkgjs.load(path ?? await projectRoot());
const inDependencies = !!pkgJson.content.dependencies?.[name];
const inDevDependencies = !!pkgJson.content.devDependencies?.[name];
const inPeerDependencies = !!pkgJson.content.peerDependencies?.[name];
return inDependencies || inDevDependencies || inPeerDependencies;
}
//#endregion
//#region src/package-manager/set-name.ts
/**
* Sets the name field in the package.json file
*
* @param name - The new name to set in the package.json file
* @param path - The path to the project root directory. If not provided, will be automatically determined
* @returns A promise that resolves when the package.json has been updated
* @throws If unable to read, update or save the package.json file
* @example
* import { setName } from "@settlemint/sdk-utils/package-manager";
*
* await setName("my-new-project-name");
*/
async function setName(name, path) {
const pkgJson = await pkgjs.load(path ?? await projectRoot());
pkgJson.update({ name });
await pkgJson.save();
}
//#endregion
export { emptyDir, formatTargetDir, getPackageManager, getPackageManagerExecutable, installDependencies, isEmpty, isPackageInstalled, setName };
//# sourceMappingURL=package-manager.js.map