UNPKG

@fromsvenwithlove/devops-issues-cli

Version:

AI-powered CLI tool and library for Azure DevOps work item management with Claude agents

334 lines (294 loc) • 9.96 kB
import chalk from 'chalk'; import inquirer from 'inquirer'; import { writeFileSync } from 'fs'; import { templates, getTemplateList, getTemplatesByCategory, createFromTemplate, categories } from '../../templates/index.js'; export default async function templatesCommand(action, options = {}) { switch (action) { case 'list': await listTemplates(options); break; case 'show': await showTemplate(options); break; case 'generate': await generateFromTemplate(options); break; case undefined: await interactiveTemplateMenu(); break; default: console.log(chalk.red(`Unknown action: ${action}`)); console.log(chalk.blue('Available actions: list, show, generate')); process.exit(1); } } async function listTemplates(options) { console.log(chalk.blue('šŸ“‹ Available Templates:\n')); const allTemplates = getTemplateList(); if (options.category) { const filtered = getTemplatesByCategory(options.category); if (filtered.length === 0) { console.log(chalk.yellow(`No templates found for category: ${options.category}`)); console.log(chalk.blue(`Available categories: ${Object.keys(categories).join(', ')}`)); return; } displayTemplates(filtered); } else { // Group by category const grouped = {}; allTemplates.forEach(template => { const category = template.category || 'other'; if (!grouped[category]) grouped[category] = []; grouped[category].push(template); }); Object.entries(grouped).forEach(([category, templates]) => { const categoryName = categories[category] || category; console.log(chalk.cyan(`${categoryName}:`)); displayTemplates(templates, ' '); console.log(''); }); } } function displayTemplates(templates, indent = '') { templates.forEach(template => { console.log(`${indent}${chalk.green(template.key)} - ${template.name}`); console.log(`${indent} ${chalk.gray(template.description)}`); console.log(`${indent} ${chalk.blue(`File: ${template.file}`)}`); }); } async function showTemplate(options) { let templateKey = options.template; if (!templateKey) { const templates = getTemplateList(); const { selected } = await inquirer.prompt([{ type: 'list', name: 'selected', message: 'Select a template to view:', choices: templates.map(t => ({ name: `${t.name} - ${t.description}`, value: t.key })) }]); templateKey = selected; } const template = templates[templateKey]; if (!template) { console.log(chalk.red(`Template '${templateKey}' not found`)); process.exit(1); } console.log(chalk.blue(`šŸ“‹ Template: ${template.name}`)); console.log(chalk.gray(`Description: ${template.description}`)); console.log(chalk.gray(`Category: ${categories[template.category] || template.category}`)); console.log(''); try { const content = template.content; if (options.json) { console.log(JSON.stringify(content, null, 2)); } else { console.log(chalk.blue('Structure:')); printTemplateStructure(content); if (options.full) { console.log(chalk.blue('\nFull JSON:')); console.log(JSON.stringify(content, null, 2)); } } } catch (error) { console.log(chalk.red(`Error loading template: ${error.message}`)); } } async function generateFromTemplate(options) { let templateKey = options.template; if (!templateKey) { const templates = getTemplateList(); const { selected } = await inquirer.prompt([{ type: 'list', name: 'selected', message: 'Select a template to generate from:', choices: templates.map(t => ({ name: `${t.name} - ${t.description}`, value: t.key })) }]); templateKey = selected; } const template = templates[templateKey]; if (!template) { console.log(chalk.red(`Template '${templateKey}' not found`)); process.exit(1); } console.log(chalk.blue(`šŸŽÆ Generating from template: ${template.name}`)); // Get customization options const overrides = await getTemplateOverrides(template); // Generate the work items let workItems; try { workItems = createFromTemplate(templateKey, overrides); } catch (error) { console.log(chalk.red(`Error generating template: ${error.message}`)); process.exit(1); } // Get output filename let outputFile = options.output; if (!outputFile) { const { filename } = await inquirer.prompt([{ type: 'input', name: 'filename', message: 'Output filename:', default: `${templateKey}-workitems.json`, validate: input => input.trim().length > 0 || 'Filename is required' }]); outputFile = filename; } // Write to file try { writeFileSync(outputFile, JSON.stringify(workItems, null, 2), 'utf-8'); console.log(chalk.green(`āœ“ Generated work items saved to: ${outputFile}`)); // Show next steps console.log(chalk.blue('\nNext steps:')); console.log(` 1. Review: ${chalk.yellow(`cat ${outputFile}`)}`); console.log(` 2. Validate: ${chalk.yellow(`npx devops-issues validate ${outputFile}`)}`); console.log(` 3. Preview: ${chalk.yellow(`npx devops-issues bulk-create ${outputFile} --preview`)}`); console.log(` 4. Create: ${chalk.yellow(`npx devops-issues bulk-create ${outputFile}`)}`); } catch (error) { console.log(chalk.red(`Error writing file: ${error.message}`)); process.exit(1); } } async function getTemplateOverrides(template) { console.log(chalk.blue('\nšŸ”§ Customize template settings:')); const overrides = { metadata: { defaults: {} } }; // Get basic overrides const answers = await inquirer.prompt([ { type: 'input', name: 'parentId', message: 'Parent Work Item ID (required - Epic, Feature, or other parent):', validate: input => { if (!input || input.trim().length === 0) { return 'Parent ID is required for all work items'; } const trimmed = input.trim(); if (!/^\d+$/.test(trimmed) && trimmed !== 'PARENT_WORK_ITEM_ID') { return 'Parent ID must be a number (e.g., 12345) or placeholder text'; } return true; }, filter: input => input.trim() }, { type: 'input', name: 'areaPath', message: 'Area Path (e.g., YourProject\\YourArea):', default: 'YourProject\\YourArea' }, { type: 'input', name: 'iterationPath', message: 'Iteration Path (e.g., YourProject\\Sprint 1):', default: 'YourProject\\Sprint 1' }, { type: 'input', name: 'assignedTo', message: 'Default assignee (email):', default: 'user@company.com' }, { type: 'list', name: 'priority', message: 'Default priority:', choices: [ { name: '1 - Highest', value: 1 }, { name: '2 - High', value: 2 }, { name: '3 - Medium', value: 3 }, { name: '4 - Low', value: 4 } ], default: 2 }, { type: 'input', name: 'tags', message: 'Default tags (semicolon separated):', default: 'generated;template' } ]); overrides.metadata.parentId = answers.parentId; overrides.metadata.areaPath = answers.areaPath; overrides.metadata.iterationPath = answers.iterationPath; overrides.metadata.defaults.assignedTo = answers.assignedTo; overrides.metadata.defaults.priority = answers.priority; overrides.metadata.defaults.tags = answers.tags; return overrides; } async function interactiveTemplateMenu() { const choices = [ { name: 'šŸ“‹ List all templates', value: 'list' }, { name: 'šŸ‘ļø Show template details', value: 'show' }, { name: 'šŸŽÆ Generate from template', value: 'generate' }, { name: 'āŒ Exit', value: 'exit' } ]; const { action } = await inquirer.prompt([{ type: 'list', name: 'action', message: 'What would you like to do?', choices }]); if (action === 'exit') { return; } switch (action) { case 'list': await listTemplates({}); break; case 'show': await showTemplate({}); break; case 'generate': await generateFromTemplate({}); break; } } function printTemplateStructure(template) { if (template.metadata) { console.log(chalk.cyan(' Metadata:')); if (template.metadata.areaPath) { console.log(` Area Path: ${chalk.yellow(template.metadata.areaPath)}`); } if (template.metadata.iterationPath) { console.log(` Iteration: ${chalk.yellow(template.metadata.iterationPath)}`); } if (template.metadata.defaults) { const defaults = Object.entries(template.metadata.defaults) .map(([key, value]) => `${key}: ${value}`) .join(', '); console.log(` Defaults: ${chalk.yellow(defaults)}`); } } if (template.workItems) { console.log(chalk.cyan(' Work Items:')); printWorkItemStructure(template.workItems, 2); } } function printWorkItemStructure(items, depth = 0) { if (!items || !Array.isArray(items)) return; const indent = ' '.repeat(depth); items.forEach((item, index) => { const typeColor = getTypeColor(item.type); const title = item.title ? item.title.substring(0, 50) : 'No title'; const truncated = item.title && item.title.length > 50 ? '...' : ''; console.log(`${indent}${chalk.gray(`${index + 1}.`)} ${typeColor(item.type)}: ${chalk.white(title)}${chalk.gray(truncated)}`); if (item.children && item.children.length > 0) { printWorkItemStructure(item.children, depth + 1); } }); } function getTypeColor(type) { const colors = { 'Epic': chalk.magenta, 'Feature': chalk.blue, 'User Story': chalk.green, 'Task': chalk.yellow, 'Bug': chalk.red }; return colors[type] || chalk.white; }