UNPKG

@letanure/resend-cli

Version:

A command-line interface for Resend email API

204 lines 7.9 kB
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