UNPKG

@curvenote/cli

Version:
326 lines (325 loc) • 11.2 kB
import chalk from 'chalk'; import inquirer from 'inquirer'; import { writeConfigs } from 'myst-cli'; import { lookupAuthor } from './peopleLookup.js'; import { cleanProjectConfigForWrite } from '../utils.js'; import { loadTemplateFile } from './loadTemplateFile.js'; import { DEFAULT_TEMPLATE_INIT_QUESTIONS } from './templateInitQuestions.js'; /** * Handle --improve: Update existing project by re-answering template questions */ export async function handleImproveProject(session, currentPath, projectConfig) { session.log.info(chalk.bold('\nšŸ”„ Improving existing project configuration...\n')); // Load template questions (from template.yml or defaults) let questionSpecs = loadTemplateFile(session, currentPath); if (!questionSpecs) { questionSpecs = DEFAULT_TEMPLATE_INIT_QUESTIONS; } const updates = {}; const changes = []; for (const spec of questionSpecs) { if (!spec.enabled) { continue; } // Get current value from projectConfig const fieldParts = spec.field.split('.'); let currentValue; if (fieldParts[0] === 'project' && fieldParts.length === 2) { currentValue = projectConfig[fieldParts[1]]; } let newValue; switch (spec.type) { case 'text': newValue = await promptImproveTextQuestion(spec, currentValue); break; case 'list': newValue = await promptImproveListQuestion(spec, currentValue); break; case 'people': newValue = await promptImprovePeopleQuestion(session, spec, currentValue); break; default: session.log.warn(`Unknown question type: ${spec.type}`); continue; } // Store the value if it changed if (newValue !== undefined) { const fieldName = fieldParts[1]; updates[fieldName] = newValue; // Track what changed if (JSON.stringify(currentValue) !== JSON.stringify(newValue)) { changes.push(` • ${spec.id}: ${formatChangeDescription(currentValue, newValue)}`); } } } // Show summary of changes if (changes.length === 0) { session.log.info(chalk.dim('\nNo changes were made.')); return; } console.log(chalk.bold('\nšŸ“‹ Summary of changes:\n')); changes.map((change) => console.log(change)); // Ask for confirmation const confirm = await inquirer.prompt([ { name: 'proceed', type: 'confirm', message: '\nSave these changes to curvenote.yml?', default: true, }, ]); if (!confirm.proceed) { session.log.info(chalk.dim('\nChanges discarded.')); return; } // Apply updates const updatedProjectConfig = { ...projectConfig, ...updates, }; await writeConfigs(session, currentPath, { projectConfig: cleanProjectConfigForWrite(updatedProjectConfig), }); session.log.info(chalk.green('\nāœ… Project configuration updated successfully!')); } /** * Prompt for a text question with existing value as default */ async function promptImproveTextQuestion(spec, currentValue) { if (spec.hint) { console.log(chalk.gray(`\n${spec.hint}`)); } const defaultValue = currentValue || spec.default; let message = spec.message; if (!spec.required) { if (defaultValue) { message = `${spec.message} ${chalk.dim('(press Enter to keep)')}`; } else { message = `${spec.message} ${chalk.dim('(press Enter to skip)')}`; } } const response = await inquirer.prompt([ { name: 'value', type: 'input', message, default: defaultValue, validate: (input) => { if (spec.required && !input.trim() && !defaultValue) { return `${spec.id} is required`; } return true; }, }, ]); const trimmed = response.value?.trim(); if (!trimmed && !defaultValue) { return undefined; } return trimmed || defaultValue; } /** * Prompt for a list question with existing values as default */ async function promptImproveListQuestion(spec, currentValue) { if (spec.hint) { console.log(chalk.gray(`\n${spec.hint}`)); } const defaultValue = currentValue?.join(', ') || spec.default; let message = spec.message; if (!spec.required) { if (defaultValue) { message = `${spec.message} ${chalk.dim('(press Enter to keep)')}`; } else { message = `${spec.message} ${chalk.dim('(press Enter to skip)')}`; } } const response = await inquirer.prompt([ { name: 'value', type: 'input', message, default: defaultValue, validate: (input) => { if (spec.required && !input.trim() && !defaultValue) { return `${spec.id} is required`; } return true; }, }, ]); const trimmed = response.value?.trim(); if (!trimmed && !defaultValue) { return undefined; } const input = trimmed || defaultValue || ''; const items = input .split(',') .map((s) => s.trim()) .filter(Boolean); return items.length > 0 ? items : undefined; } /** * Prompt for a people question - show existing people, then ask to add more */ async function promptImprovePeopleQuestion(session, spec, currentValue) { if (spec.hint) { console.log(chalk.gray(`\n${spec.hint}`)); } // Show existing people if (currentValue && currentValue.length > 0) { console.log(chalk.bold('\nExisting people:')); currentValue.forEach((person, index) => { const name = person.name || 'Unknown'; console.log(chalk.dim(` ${index + 1}. ${name}`)); }); } else { console.log(chalk.dim('\nNo existing people.')); } // Ask if they want to add more const addMorePrompt = await inquirer.prompt([ { name: 'addMore', type: 'confirm', message: 'Add more people?', default: false, }, ]); if (!addMorePrompt.addMore) { return currentValue; } // Collect new people const newPeople = []; // eslint-disable-next-line no-constant-condition while (true) { const identifierPrompt = await inquirer.prompt([ { name: 'identifier', type: 'input', message: 'Enter ORCID or GitHub username (or press Enter to finish):', }, ]); if (!identifierPrompt.identifier?.trim()) { break; } const person = await lookupAuthor(session, identifierPrompt.identifier); if (person) { console.log(chalk.green(` āœ“ Found: ${person.name}`)); if (person.orcid) console.log(chalk.dim(` ORCID: ${person.orcid}`)); if (person.github) console.log(chalk.dim(` GitHub: ${person.github}`)); if (person.email) console.log(chalk.dim(` Email: ${person.email}`)); const confirm = await inquirer.prompt([ { name: 'add', type: 'confirm', message: 'Add this person?', default: true, }, ]); if (confirm.add) { newPeople.push(person); } } else { console.log(chalk.yellow(' Could not find person information.')); const manualPrompt = await inquirer.prompt([ { name: 'manual', type: 'confirm', message: 'Enter person information manually?', default: false, }, ]); if (manualPrompt.manual) { const manualPerson = await inquirer.prompt([ { name: 'name', type: 'input', message: 'Name:', validate: (input) => (input.trim() ? true : 'Name is required'), }, { name: 'email', type: 'input', message: 'Email (optional):', }, { name: 'corresponding', type: 'confirm', message: 'Corresponding?', default: false, when: (answers) => !!answers.email, }, ]); newPeople.push({ name: manualPerson.name, email: manualPerson.email || undefined, corresponding: manualPerson.corresponding || false, }); } } // Ask if they want to add more const continuePrompt = await inquirer.prompt([ { name: 'continue', type: 'confirm', message: 'Add another person?', default: false, }, ]); if (!continuePrompt.continue) { break; } } // Combine existing and new people if (newPeople.length > 0) { return [...(currentValue || []), ...newPeople]; } return currentValue; } /** * Format a change description for display */ function formatChangeDescription(oldValue, newValue) { if (oldValue === undefined || oldValue === null) { return `added "${formatValue(newValue)}"`; } if (newValue === undefined || newValue === null) { return `removed`; } if (Array.isArray(oldValue) && Array.isArray(newValue)) { if (oldValue.length === 0) { return `added ${newValue.length} item(s)`; } if (newValue.length > oldValue.length) { return `added ${newValue.length - oldValue.length} item(s) (was ${oldValue.length}, now ${newValue.length})`; } if (newValue.length < oldValue.length) { return `removed ${oldValue.length - newValue.length} item(s) (was ${oldValue.length}, now ${newValue.length})`; } return `modified (${newValue.length} items)`; } return `changed from "${formatValue(oldValue)}" to "${formatValue(newValue)}"`; } /** * Format a value for display (truncate if too long) */ function formatValue(value) { if (Array.isArray(value)) { if (value.length === 0) return '(empty)'; if (value.length > 3) return `${value.slice(0, 3).join(', ')}...`; return value.join(', '); } const str = String(value); if (str.length > 50) return str.substring(0, 47) + '...'; return str; }