@shopify/cli-kit
Version:
A set of utilities, interfaces, and models that are common across all the platform features
370 lines • 12.2 kB
JavaScript
import { isUnitTest, isVerbose } from './context/local.js';
import { currentProcessIsGlobal } from './is-global.js';
import colors from './colors.js';
import { isTruthy } from './context/utilities.js';
import { ColorContentToken, CommandContentToken, ErrorContentToken, HeadingContentToken, ItalicContentToken, JsonContentToken, LinesDiffContentToken, LinkContentToken, PathContentToken, RawContentToken, SubHeadingContentToken, } from '../../private/node/content-tokens.js';
import { tokenItemToString } from '../../private/node/ui/components/TokenizedText.js';
import { consoleLog, consoleWarn, output } from '../../private/node/output.js';
import stripAnsi from 'strip-ansi';
import { Writable } from 'stream';
export class TokenizedString {
constructor(value) {
this.value = value;
}
}
export const outputToken = {
raw(value) {
return new RawContentToken(value);
},
genericShellCommand(value) {
return new CommandContentToken(value);
},
json(value) {
return new JsonContentToken(value);
},
path(value) {
return new PathContentToken(value);
},
link(value, link, fallback) {
return new LinkContentToken(value, link, fallback);
},
heading(value) {
return new HeadingContentToken(value);
},
subheading(value) {
return new SubHeadingContentToken(value);
},
italic(value) {
return new ItalicContentToken(value);
},
errorText(value) {
return new ErrorContentToken(value);
},
cyan(value) {
return new ColorContentToken(value, colors.cyan);
},
yellow(value) {
return new ColorContentToken(value, colors.yellow);
},
magenta(value) {
return new ColorContentToken(value, colors.magenta);
},
green(value) {
return new ColorContentToken(value, colors.green);
},
gray(value) {
return new ColorContentToken(value, colors.gray);
},
packagejsonScript(packageManager, scriptName, ...scriptArgs) {
return new CommandContentToken(formatPackageManagerCommand(packageManager, scriptName, ...scriptArgs));
},
successIcon() {
return new ColorContentToken('✔', colors.green);
},
failIcon() {
return new ErrorContentToken('✖');
},
linesDiff(value) {
return new LinesDiffContentToken(value);
},
};
/**
* Given a command and its arguments, it formats it depending on the package manager.
*
* @param packageManager - The package manager to use (pnpm, npm, yarn).
* @param scriptName - The name of the script to run.
* @param scriptArgs - The arguments to pass to the script.
* @returns The formatted command.
*/
export function formatPackageManagerCommand(packageManager, scriptName, ...scriptArgs) {
if (currentProcessIsGlobal()) {
return [scriptName, ...scriptArgs].join(' ');
}
switch (packageManager) {
case 'pnpm':
case 'bun':
case 'yarn': {
const pieces = [packageManager, scriptName, ...scriptArgs];
return pieces.join(' ');
}
case 'npm': {
const pieces = ['npm', 'run', scriptName];
if (scriptArgs.length > 0) {
pieces.push('--');
pieces.push(...scriptArgs);
}
return pieces.join(' ');
}
case 'unknown': {
const pieces = [scriptName, ...scriptArgs];
return pieces.join(' ');
}
}
}
/**
* Creates a tokenized string from an array of strings and tokens.
*
* @param strings - The strings to join.
* @param keys - Array of tokens or strings to join.
* @returns The tokenized string.
*/
export function outputContent(strings, ...keys) {
let output = ``;
strings.forEach((string, i) => {
output += string;
if (i >= keys.length) {
return;
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const token = keys[i];
if (typeof token === 'string') {
output += token;
}
else if (token) {
const enumTokenOutput = token.output();
if (Array.isArray(enumTokenOutput)) {
enumTokenOutput.forEach((line) => {
output += line;
});
}
else {
output += enumTokenOutput;
}
}
});
return new TokenizedString(output);
}
/**
* It maps a level to a numeric value.
*
* @param level - The level for which we'll return its numeric value.
* @returns The numeric value of the level.
*/
function logLevelValue(level) {
switch (level) {
case 'trace':
return 10;
case 'debug':
return 20;
case 'info':
return 30;
case 'warn':
return 40;
case 'error':
return 50;
case 'fatal':
return 60;
default:
return 30;
}
}
/**
* It returns the current log level (debug or info).
*
* @returns The log level set by the user.
*/
function currentLogLevel() {
if (isVerbose()) {
return 'debug';
}
else {
return 'info';
}
}
/**
* It checks if the message should be outputted or not.
*
* @param logLevel - The desired log level for the message.
* @returns True if the message should be outputted, false otherwise.
*/
function shouldOutput(logLevel) {
if (isUnitTest()) {
return false;
}
const currentLogLevelValue = logLevelValue(currentLogLevel());
const messageLogLevelValue = logLevelValue(logLevel);
return messageLogLevelValue >= currentLogLevelValue;
}
// eslint-disable-next-line import/no-mutable-exports
export let collectedLogs = {};
/**
* This is only used during UnitTesting.
* If we are in a testing context, instead of printing the logs to the console,
* we will store them in a variable that can be accessed from the tests.
*
* @param key - The key of the log.
* @param content - The content of the log.
*/
export function collectLog(key, content) {
const output = collectedLogs.output ?? [];
const data = collectedLogs[key] ?? [];
data.push(stripAnsi(stringifyMessage(content) ?? ''));
output.push(stripAnsi(stringifyMessage(content) ?? ''));
collectedLogs[key] = data;
collectedLogs.output = output;
}
export const clearCollectedLogs = () => {
collectedLogs = {};
};
/**
* Outputs command result information to the user.
* Result messages don't get additional formatting.
* The messages are logged at info level to stdout.
*
* @param content - The content to be output to the user.
*/
export function outputResult(content) {
output(content, 'info', consoleLog);
}
/**
* Logs information at the info level.
* Info messages don't get additional formatting.
* Note: By default, info messages are sent through the standard error.
*
* @param content - The content to be output to the user.
* @param logger - The logging function to use to output to the user.
*/
export function outputInfo(content, logger = consoleWarn) {
const message = stringifyMessage(content);
if (isUnitTest())
collectLog('info', content);
outputWhereAppropriate('info', logger, message);
}
/**
* Outputs a success message to the user.
* Success messages receive a special formatting to make them stand out in the console.
* Note: Success messages are sent through the standard error.
*
* @param content - The content to be output to the user.
* @param logger - The logging function to use to output to the user.
*/
export function outputSuccess(content, logger = consoleWarn) {
const message = colors.bold(`✅ Success! ${stringifyMessage(content)}.`);
if (isUnitTest())
collectLog('success', content);
outputWhereAppropriate('info', logger, message);
}
/**
* Outputs a completed message to the user.
* Completed message receive a special formatting to make them stand out in the console.
* Note: Completed messages are sent through the standard error.
*
* @param content - The content to be output to the user.
* @param logger - The logging function to use to output to the user.
*/
export function outputCompleted(content, logger = consoleWarn) {
const message = `${colors.green('✔')} ${stringifyMessage(content)}`;
if (isUnitTest())
collectLog('completed', content);
outputWhereAppropriate('info', logger, message);
}
/**
* Logs a message at the debug level. By default these output is hidden unless the user calls the CLI with --verbose.
* Debug messages don't get additional formatting.
* Note: By default, debug messages are sent through the standard error.
*
* @param content - The content to be output to the user.
* @param logger - The logging function to use to output to the user.
*/
export function outputDebug(content, logger = consoleWarn) {
if (isUnitTest())
collectLog('debug', content);
const message = colors.gray(stringifyMessage(content));
outputWhereAppropriate('debug', logger, `${new Date().toISOString()}: ${message}`);
}
/**
* Logs a message at the warning level.
* Warning messages receive a special formatting to make them stand out in the console.
* Note: By default, warning messages are sent through the standard error.
*
* @param content - The content to be output to the user.
* @param logger - The logging function to use to output to the user.
*/
export function outputWarn(content, logger = consoleWarn) {
if (isUnitTest())
collectLog('warn', content);
const message = colors.yellow(stringifyMessage(content));
outputWhereAppropriate('warn', logger, message);
}
/**
* Prints a new line in the terminal.
*/
export function outputNewline() {
consoleWarn('');
}
/**
* Converts a Message to string.
*
* @param message - The message to convert to string.
* @returns The string representation of the message.
*/
export function stringifyMessage(message) {
if (message instanceof TokenizedString) {
return message.value;
}
else {
return message;
}
}
/**
* Convert a TokenItem to string.
*
* @param item - The item to convert to string.
* @returns The string representation of the item.
*/
export function itemToString(item) {
return tokenItemToString(item);
}
/**
* Writes a message to the appropiated logger.
*
* @param logLevel - The log level to use to determine if the message should be output.
* @param logger - The logger to use to output the message.
* @param message - The message to output.
*/
export function outputWhereAppropriate(logLevel, logger, message) {
if (shouldOutput(logLevel)) {
if (logger instanceof Writable) {
logger.write(message);
}
else {
logger(message, logLevel);
}
}
}
/**
* Returns a message without styles (colors or any ANSI escape codes).
*
* @param message - The message to remove styles from.
* @returns The message without styles.
*/
export function unstyled(message) {
return stripAnsi(message);
}
/**
* Checks if the console outputs should display colors or not.
*
* @param _process - Optional, the process-like object to use to check if the console should display colors. Defaults to the global process.
* @returns True if the console outputs should display colors, false otherwise.
*/
export function shouldDisplayColors(_process = process) {
const { env, stdout } = _process;
if (Object.hasOwnProperty.call(env, 'FORCE_COLOR')) {
return isTruthy(env.FORCE_COLOR);
}
else {
return Boolean(stdout.isTTY);
}
}
/**
* Parse title and body to be a single formatted string.
*
* @param title - The title of the message. Will be formatted as a heading.
* @param body - The body of the message. Will respect the original formatting.
* @returns The formatted message.
*/
export function formatSection(title, body) {
const formattedTitle = `${title.toUpperCase()}${' '.repeat(35 - title.length)}`;
return outputContent `${outputToken.heading(formattedTitle)}\n${body}`.value;
}
//# sourceMappingURL=output.js.map