@letanure/resend-cli
Version:
A command-line interface for Resend email API
204 lines • 7.9 kB
JavaScript
import chalk from 'chalk';
import { formatDataWithFields, formatForCLI } from './display-formatter.js';
import { formatAsTable } from './table-formatter.js';
import { displayInvalidOptionError, displayMissingEnvError, displayUnknownOptionError } from './error-formatting.js';
import { outputSuccess, outputValidationErrors } from './output.js';
// Validate options using a Zod schema
export function validateOptions(options, schema, format = 'text', fields = [], command) {
// Use options directly without transformation
// Commander.js already converts kebab-case to camelCase
const validationResult = schema.safeParse(options);
if (!validationResult.success) {
const errors = validationResult.error.issues.map((issue) => ({
path: issue.path[0] || 'unknown',
message: issue.message,
}));
outputValidationErrors(errors, format, () => {
displayValidationErrors(errors, fields, command);
});
process.exit(1);
}
return validationResult.data;
}
/**
* Console output utilities for CLI results
*/
function logCliResults(data, fields, title, additionalInfo, successMessage) {
// Format the main data using the shared formatter
const formattedFields = formatDataWithFields(data, fields);
// Add additional info that's not already shown
if (additionalInfo) {
for (const [key, value] of Object.entries(additionalInfo)) {
if (value && key !== 'ID') {
// Skip ID since it's already shown as Email ID
formattedFields.push({ label: key, value });
}
}
}
// Show title with colors
console.log(chalk.green(`✓ ${title}`));
// If we have data to show, format and display it
if (formattedFields.length > 0) {
const output = formatForCLI(formattedFields, '');
console.log(output);
}
// Always show success message if provided
if (successMessage) {
console.log(chalk.yellow(`${successMessage}`));
}
}
// Display list results with proper table formatting
function logCliListResults(data, fields, title, additionalInfo, successMessage) {
console.log(chalk.green(`✓ ${title}`));
console.log('');
const tableOutput = formatAsTable(data, fields);
console.log(tableOutput);
if (additionalInfo) {
console.log('');
for (const [key, value] of Object.entries(additionalInfo)) {
if (value && key !== 'ID') {
console.log(` ${key.padEnd(15)}: ${value}`);
}
}
}
if (successMessage) {
console.log('');
console.log(chalk.yellow(`${successMessage}`));
}
}
// Display parsed CLI data using field configuration
export function displayCLIResults(data, fields, format = 'text', title = 'Parsed data:', additionalInfo, successMessage) {
// For JSON output, show raw data without modifications
if (format === 'json') {
outputSuccess(data, format, () => {
// No text callback needed for JSON output
});
return;
}
outputSuccess(data, format, () => {
if (Array.isArray(data)) {
logCliListResults(data, fields, title, additionalInfo, successMessage);
}
else {
logCliResults(data, fields, title, additionalInfo, successMessage);
}
});
}
// Display CLI errors with colors
export function displayCLIError(_data, _fields, format = 'text', title = 'Error:', additionalInfo, errorMessage) {
// For JSON output, use the standard error output
if (format === 'json') {
const errorData = {
success: false,
error: errorMessage || title,
...additionalInfo,
};
console.log(JSON.stringify(errorData, null, 2));
return;
}
console.error(chalk.red(`✗ ${title}`));
if (errorMessage) {
console.error(chalk.red(errorMessage));
}
if (additionalInfo) {
for (const [key, value] of Object.entries(additionalInfo)) {
if (value) {
console.error(chalk.red(`${key}: ${value}`));
}
}
}
}
// Display validation errors
export function displayValidationErrors(errors, fields = [], command) {
const errorCount = errors.length;
const title = errorCount === 1 ? 'Validation Error' : 'Validation Errors';
console.error(chalk.red(`✗ ${title}`));
const fieldMapping = buildFieldToFlagMapping(fields);
for (const error of errors) {
const flagName = fieldMapping[String(error.path)] || `--${String(error.path)}`;
console.error(chalk.red(` ${flagName} is required`));
}
console.error('');
if (command) {
command.help();
}
else {
console.error(chalk.cyan('Use --help for usage information'));
}
}
// Build field-to-flag mapping from provided fields
function buildFieldToFlagMapping(fields) {
const mapping = {};
for (const field of fields) {
if (field.name && field.cliFlag) {
// Map both the field name and snake_case version
const flagName = field.cliFlag.startsWith('--') ? field.cliFlag : `--${field.cliFlag}`;
mapping[field.name] = flagName;
// Also map snake_case version of field name
const snakeCaseName = field.name.replace(/([A-Z])/g, '_$1').toLowerCase();
if (snakeCaseName !== field.name) {
mapping[snakeCaseName] = flagName;
}
}
}
return mapping;
}
// Validate required environment variable
export function validateEnvironmentVariable(varName, helpUrl) {
const value = process.env[varName];
if (!value) {
displayMissingEnvError(varName, helpUrl, { exitCode: 1 });
}
return value;
}
// Convert field configuration to Commander.js options
export function fieldToCommanderOption(field) {
// Ensure we don't double up on dashes
const cliFlag = field.cliFlag.startsWith('--') ? field.cliFlag : `--${field.cliFlag}`;
const shortFlag = field.cliShortFlag.startsWith('-') ? field.cliShortFlag : `-${field.cliShortFlag}`;
const flags = `${cliFlag}, ${shortFlag} <value>`;
return {
flags,
description: field.helpText,
};
}
// Validate output format
function validateOutputFormat(value) {
const validFormats = ['text', 'json'];
if (!validFormats.includes(value)) {
displayInvalidOptionError(`--output ${value}`, validFormats, {
title: 'Invalid Output Format',
exitCode: 1,
});
}
return value;
}
// Register field options on a command
export function registerFieldOptions(command, fields) {
// Add global options first
command.option('--output <format>', 'Output format (text, json)', validateOutputFormat, 'text');
command.option('--dry-run', 'Validate and preview without sending');
command.option('--api-key <key>', 'Resend API key (overrides RESEND_API_KEY environment variable)');
// Add field-specific options
for (const field of fields) {
const option = fieldToCommanderOption(field);
command.option(option.flags, option.description);
}
// Configure consistent error output for all commands
command.configureOutput({
writeErr: (str) => {
// Custom error formatting for unknown options
if (str.includes('unknown option')) {
const match = str.match(/unknown option '([^']+)'/);
if (match?.[1]) {
const invalidOption = match[1];
displayUnknownOptionError(invalidOption);
// Function never returns, so this return is unreachable but kept for clarity
}
}
// Default error output for other Commander.js errors
process.stderr.write(str);
},
});
}
//# sourceMappingURL=cli.js.map