@letanure/resend-cli
Version:
A command-line interface for Resend email API
340 lines (276 loc) • 9.6 kB
JavaScript
#!/usr/bin/env node
const fs = require('node:fs');
const path = require('node:path');
/**
* Extract command structure from your CLI modules
*/
function extractCommandStructure() {
// Import module configs from shared config file
const { moduleConfigs } = require('../dist/src/config/modules.js');
const modules = {};
const modulesPath = path.join(__dirname, '../src/modules');
for (const config of moduleConfigs) {
const modulePath = path.join(modulesPath, config.folder);
modules[config.name] = {
description: config.description,
commands: extractModuleCommands(modulePath),
};
}
return modules;
}
/**
* Extract commands from a module
*/
function extractModuleCommands(modulePath) {
const commands = {};
// Look for command directories (send, retrieve, etc.)
const entries = fs.readdirSync(modulePath, { withFileTypes: true }).filter((dirent) => dirent.isDirectory());
for (const entry of entries) {
const commandPath = path.join(modulePath, entry.name);
const fieldsPath = path.join(commandPath, 'fields.ts');
if (fs.existsSync(fieldsPath)) {
commands[entry.name] = {
description: getCommandDescription(commandPath),
options: extractCommandOptions(fieldsPath),
};
}
}
return commands;
}
/**
* Get command description from CLI file
*/
function getCommandDescription(commandPath) {
try {
const cliPath = path.join(commandPath, 'cli.ts');
const content = fs.readFileSync(cliPath, 'utf8');
// Extract description from .description() call
const match = content.match(/\.description\(['"]([^'"]+)['"]/);
return match ? match[1] : '';
} catch {
return '';
}
}
/**
* Extract command options from fields.ts
*/
function extractCommandOptions(fieldsPath) {
try {
const content = fs.readFileSync(fieldsPath, 'utf8');
const options = [];
// Extract field names from sendFieldNames or similar arrays
const fieldNamesMatch = content.match(/const\s+\w+FieldNames\s*=\s*\[([^\]]+)\]/);
if (fieldNamesMatch) {
const fieldNames = fieldNamesMatch[1]
.split(',')
.map((name) => name.trim().replace(/['"]/g, ''))
.filter((name) => name);
// For each field name, we'll create basic options
for (const fieldName of fieldNames) {
options.push({
name: fieldName,
flag: `--${fieldName.replace(/([A-Z])/g, '-$1').toLowerCase()}`,
description: `${fieldName} option`,
});
}
}
return options;
} catch {
return [];
}
}
/**
* Generate bash completion script
*/
function generateBashCompletion(commands) {
const commandList = Object.keys(commands).join(' ');
let script = `#!/bin/bash
# resend-cli completion
_resend_completion() {
local cur prev opts
COMPREPLY=()
cur="\${COMP_WORDS[COMP_CWORD]}"
prev="\${COMP_WORDS[COMP_CWORD-1]}"
# Root command completions
if [[ \${COMP_CWORD} -eq 1 ]]; then
opts="${commandList} --help --version --dry-run"
COMPREPLY=( \$(compgen -W "\${opts}" -- \${cur}) )
return 0
fi
# Module-specific completions
local module="\${COMP_WORDS[1]}"
case \${module} in
`;
for (const [moduleName, moduleData] of Object.entries(commands)) {
script += ` ${moduleName})\n`;
script += ` if [[ \${COMP_CWORD} -eq 2 ]]; then\n`;
script += ` opts="${Object.keys(moduleData.commands).join(' ')}"\n`;
script += ` COMPREPLY=( \$(compgen -W "\${opts}" -- \${cur}) )\n`;
script += ` else\n`;
script += ` # Command-specific options\n`;
script += ` local command="\${COMP_WORDS[2]}"\n`;
script += ` case \${command} in\n`;
for (const [commandName, commandData] of Object.entries(moduleData.commands)) {
const flags = commandData.options.map((opt) => opt.flag).join(' ');
script += ` ${commandName})\n`;
script += ` opts="${flags}"\n`;
script += ` COMPREPLY=( \$(compgen -W "\${opts}" -- \${cur}) )\n`;
script += ` ;;\n`;
}
script += ` esac\n`;
script += ` fi\n`;
script += ` ;;\n`;
}
script += ` esac
}
complete -F _resend_completion resend-cli
complete -F _resend_completion resend
`;
return script;
}
/**
* Generate zsh completion script
*/
function generateZshCompletion(commands) {
let script = `#compdef resend-cli resend
# resend-cli completion
_resend_completion() {
local context state line
_arguments -C \\
'(--help -h)'{--help,-h}'[Show help]' \\
'(--version -V)'{--version,-V}'[Show version]' \\
'--dry-run[Enable dry-run mode]' \\
'1: :_resend_modules' \\
'2: :_resend_commands' \\
'*: :_resend_options'
}
_resend_modules() {
local -a modules
modules=(
`;
for (const [moduleName, moduleData] of Object.entries(commands)) {
script += ` '${moduleName}:${moduleData.description}'\n`;
}
script += ` )
_describe 'modules' modules
}
_resend_commands() {
local module=\${words[2]}
local -a commands
case \${module} in
`;
for (const [moduleName, moduleData] of Object.entries(commands)) {
script += ` ${moduleName})\n`;
script += ` commands=(\n`;
for (const [commandName, commandData] of Object.entries(moduleData.commands)) {
script += ` '${commandName}:${commandData.description}'\n`;
}
script += ` )\n`;
script += ` ;;\n`;
}
script += ` esac
_describe 'commands' commands
}
_resend_options() {
local module=\${words[2]}
local command=\${words[3]}
local -a options
case \${module} in
`;
for (const [moduleName, moduleData] of Object.entries(commands)) {
script += ` ${moduleName})\n`;
script += ` case \${command} in\n`;
for (const [commandName, commandData] of Object.entries(moduleData.commands)) {
script += ` ${commandName})\n`;
script += ` options=(\n`;
for (const option of commandData.options) {
script += ` '${option.flag}[${option.description}]'\n`;
}
script += ` )\n`;
script += ` ;;\n`;
}
script += ` esac\n`;
script += ` ;;\n`;
}
script += ` esac
_arguments \${options}
}
_resend_completion
`;
return script;
}
/**
* Generate PowerShell completion script
*/
function generatePowerShellCompletion(commands) {
let script = `# resend-cli completion
Register-ArgumentCompleter -Native -CommandName resend-cli -ScriptBlock {
param($commandName, $wordToComplete, $cursorPosition)
$command = $wordToComplete.Split(' ')
$commandCount = $command.Length
# Root command completions
if ($commandCount -eq 1) {
$completions = @(
@{ CompletionText = '--help'; ToolTip = 'Show help' }
@{ CompletionText = '--version'; ToolTip = 'Show version' }
@{ CompletionText = '--dry-run'; ToolTip = 'Enable dry-run mode' }
`;
for (const [moduleName, moduleData] of Object.entries(commands)) {
script += ` @{ CompletionText = '${moduleName}'; ToolTip = '${moduleData.description}' }\n`;
}
script += ` )
return $completions | Where-Object { $_.CompletionText -like "$wordToComplete*" }
}
# Module-specific completions
$module = $command[0]
switch ($module) {
`;
for (const [moduleName, moduleData] of Object.entries(commands)) {
script += ` '${moduleName}' {\n`;
script += ` if ($commandCount -eq 2) {\n`;
script += ` $completions = @(\n`;
for (const [commandName, commandData] of Object.entries(moduleData.commands)) {
script += ` @{ CompletionText = '${commandName}'; ToolTip = '${commandData.description}' }\n`;
}
script += ` )\n`;
script += ` return $completions | Where-Object { $_.CompletionText -like "$wordToComplete*" }\n`;
script += ` } else {\n`;
script += ` $subCommand = $command[1]\n`;
script += ` switch ($subCommand) {\n`;
for (const [commandName, commandData] of Object.entries(moduleData.commands)) {
script += ` '${commandName}' {\n`;
script += ` $completions = @(\n`;
for (const option of commandData.options) {
script += ` @{ CompletionText = '${option.flag}'; ToolTip = '${option.description}' }\n`;
}
script += ` )\n`;
script += ` return $completions | Where-Object { $_.CompletionText -like "$wordToComplete*" }\n`;
script += ` }\n`;
}
script += ` }\n`;
script += ` }\n`;
script += ` }\n`;
}
script += ` }
}
# Also register for 'resend' alias
Register-ArgumentCompleter -Native -CommandName resend -ScriptBlock {
param($commandName, $wordToComplete, $cursorPosition)
# Call the same completion logic
& $MyInvocation.MyCommand.ScriptBlock 'resend-cli' $wordToComplete $cursorPosition
}
`;
return script;
}
/**
* Main function to generate all completion scripts
*/
async function generateCompletions() {
const commands = extractCommandStructure();
return {
bash: generateBashCompletion(commands),
zsh: generateZshCompletion(commands),
powershell: generatePowerShellCompletion(commands),
};
}
module.exports = { generateCompletions, extractCommandStructure };