This commit is contained in:
310
src/commands/template.ts
Normal file
310
src/commands/template.ts
Normal file
@@ -0,0 +1,310 @@
|
||||
import inquirer from 'inquirer';
|
||||
import { fileUtils, templateUtils } from '../utils';
|
||||
import { Template, Layout } from '../models/types';
|
||||
|
||||
interface TemplateOptions {
|
||||
name?: string;
|
||||
list?: boolean;
|
||||
create?: boolean;
|
||||
delete?: boolean;
|
||||
apply?: string;
|
||||
variables?: Record<string, string>;
|
||||
}
|
||||
|
||||
export async function manageTemplates(options: TemplateOptions): Promise<void> {
|
||||
await fileUtils.initialize();
|
||||
|
||||
if (options.list) {
|
||||
await listTemplates();
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.delete) {
|
||||
await deleteTemplate(options.name!);
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.create) {
|
||||
await createTemplateInteractive();
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.apply) {
|
||||
await applyTemplate(options.apply, options.variables);
|
||||
return;
|
||||
}
|
||||
|
||||
await listTemplatesInteractive();
|
||||
}
|
||||
|
||||
async function listTemplates(): Promise<void> {
|
||||
const templates = await fileUtils.listTemplates();
|
||||
if (templates.length === 0) {
|
||||
console.log('No templates found. Use "template --create" to create one.');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Available Templates:');
|
||||
console.log('-'.repeat(50));
|
||||
|
||||
for (const templateFile of templates) {
|
||||
try {
|
||||
const template = await fileUtils.readTemplate(templateFile) as Template;
|
||||
console.log(` ${template.displayName}`);
|
||||
console.log(` Name: ${template.name}`);
|
||||
console.log(` Description: ${template.description}`);
|
||||
console.log(` Variables: ${template.variables.length}`);
|
||||
console.log('');
|
||||
} catch {
|
||||
console.log(` ${templateFile} (error reading)`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function listTemplatesInteractive(): Promise<void> {
|
||||
const templates = await fileUtils.listTemplates();
|
||||
if (templates.length === 0) {
|
||||
console.log('No templates found. Use "template --create" to create one.');
|
||||
return;
|
||||
}
|
||||
|
||||
const { selectedTemplate } = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'selectedTemplate',
|
||||
message: 'Select a template:',
|
||||
choices: [
|
||||
...templates.map((t) => ({ name: t.replace(/\.(json|yaml|yml)$/, ''), value: t })),
|
||||
new inquirer.Separator(),
|
||||
{ name: 'Create new template', value: '__create__' },
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
if (selectedTemplate === '__create__') {
|
||||
await createTemplateInteractive();
|
||||
return;
|
||||
}
|
||||
|
||||
await showTemplateDetails(selectedTemplate);
|
||||
}
|
||||
|
||||
async function showTemplateDetails(templateFile: string): Promise<void> {
|
||||
const template = await fileUtils.readTemplate(templateFile) as Template;
|
||||
|
||||
console.log('\nTemplate Details:');
|
||||
console.log(` Name: ${template.name}`);
|
||||
console.log(` Display Name: ${template.displayName}`);
|
||||
console.log(` Description: ${template.description}`);
|
||||
console.log(` Variables: ${template.variables.length}`);
|
||||
|
||||
if (template.variables.length > 0) {
|
||||
console.log('\nVariables:');
|
||||
for (const variable of template.variables) {
|
||||
const required = variable.required ? ' (required)' : '';
|
||||
const defaultValue = variable.defaultValue ? ` = ${variable.defaultValue}` : '';
|
||||
console.log(` - ${variable.name}${required}${defaultValue}`);
|
||||
console.log(` ${variable.description}`);
|
||||
}
|
||||
}
|
||||
|
||||
const { action } = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'action',
|
||||
message: 'What would you like to do?',
|
||||
choices: [
|
||||
{ name: 'Apply template', value: 'apply' },
|
||||
{ name: 'View layout', value: 'view' },
|
||||
{ name: 'Back', value: 'back' },
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
if (action === 'apply') {
|
||||
await applyTemplate(template.name);
|
||||
} else if (action === 'view') {
|
||||
console.log('\nTemplate Layout:');
|
||||
console.log(JSON.stringify(template.layout, null, 2));
|
||||
}
|
||||
}
|
||||
|
||||
async function createTemplateInteractive(): Promise<void> {
|
||||
const { templateName, displayName, description } = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'templateName',
|
||||
message: 'Enter template name (kebab-case):',
|
||||
validate: (input: string) => /^[a-z0-9-]+$/.test(input) || 'Use kebab-case (lowercase with hyphens)',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'displayName',
|
||||
message: 'Enter display name:',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'description',
|
||||
message: 'Enter description:',
|
||||
},
|
||||
]);
|
||||
|
||||
const layouts = await fileUtils.listLayouts();
|
||||
if (layouts.length === 0) {
|
||||
console.log('No layouts found. Create a layout first, then save it as a template.');
|
||||
return;
|
||||
}
|
||||
|
||||
const { sourceLayout } = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'sourceLayout',
|
||||
message: 'Select a layout to use as template:',
|
||||
choices: layouts,
|
||||
},
|
||||
]);
|
||||
|
||||
const layout = await fileUtils.readLayout(sourceLayout) as Layout;
|
||||
|
||||
const variables: Record<string, string> = {};
|
||||
const { addVariable } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'addVariable',
|
||||
message: 'Would you like to add template variables?',
|
||||
default: false,
|
||||
},
|
||||
]);
|
||||
|
||||
if (addVariable) {
|
||||
const variableList: Array<{ name: string; description: string; defaultValue: string }> = [];
|
||||
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const { name } = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'name',
|
||||
message: 'Enter variable name (e.g., PROJECT_DIR):',
|
||||
},
|
||||
]);
|
||||
|
||||
const { description } = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'description',
|
||||
message: 'Enter variable description:',
|
||||
},
|
||||
]);
|
||||
|
||||
const { hasDefault } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'hasDefault',
|
||||
message: 'Does this variable have a default value?',
|
||||
default: false,
|
||||
},
|
||||
]);
|
||||
|
||||
let defaultValue = '';
|
||||
if (hasDefault) {
|
||||
const { value } = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'value',
|
||||
message: 'Enter default value:',
|
||||
},
|
||||
]);
|
||||
defaultValue = value;
|
||||
}
|
||||
|
||||
variableList.push({ name, description, defaultValue });
|
||||
|
||||
const { shouldAddMore } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'shouldAddMore',
|
||||
message: 'Add another variable?',
|
||||
default: false,
|
||||
},
|
||||
]);
|
||||
|
||||
if (!shouldAddMore) break;
|
||||
}
|
||||
|
||||
for (const v of variableList) {
|
||||
variables[v.name] = `\${${v.name}}`;
|
||||
}
|
||||
}
|
||||
|
||||
const substitutedLayout = templateUtils.substituteVariables(layout, variables);
|
||||
|
||||
const template: Template = {
|
||||
name: templateName,
|
||||
displayName,
|
||||
description,
|
||||
variables: Object.entries(variables).map(([name]) => ({
|
||||
name,
|
||||
description: '',
|
||||
required: true,
|
||||
})),
|
||||
layout: substitutedLayout,
|
||||
};
|
||||
|
||||
const filePath = await fileUtils.saveTemplate(templateName, template);
|
||||
console.log(`Template created: ${filePath}`);
|
||||
}
|
||||
|
||||
async function deleteTemplate(name: string): Promise<void> {
|
||||
const templates = await fileUtils.listTemplates();
|
||||
const templateFile = templates.find((t) => t.includes(name));
|
||||
|
||||
if (!templateFile) {
|
||||
throw new Error(`Template not found: ${name}`);
|
||||
}
|
||||
|
||||
const { confirm } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'confirm',
|
||||
message: `Delete template "${name}"?`,
|
||||
default: false,
|
||||
},
|
||||
]);
|
||||
|
||||
if (confirm) {
|
||||
await fileUtils.deleteTemplate(templateFile);
|
||||
console.log(`Template deleted: ${name}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function applyTemplate(templateName: string, variables?: Record<string, string>): Promise<void> {
|
||||
const templates = await fileUtils.listTemplates();
|
||||
const templateFile = templates.find((t) => t.includes(templateName));
|
||||
|
||||
if (!templateFile) {
|
||||
throw new Error(`Template not found: ${templateName}`);
|
||||
}
|
||||
|
||||
const template = await fileUtils.readTemplate(templateFile) as Template;
|
||||
|
||||
const missingVars = templateUtils.validateTemplateVariables(template, variables || {});
|
||||
if (missingVars.length > 0) {
|
||||
console.log(`Missing required variables: ${missingVars.join(', ')}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const layout = templateUtils.substituteVariables(template.layout, variables || {});
|
||||
|
||||
const { saveAs } = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'saveAs',
|
||||
message: 'Save as layout name:',
|
||||
default: `${template.name}-instance`,
|
||||
},
|
||||
]);
|
||||
|
||||
const filePath = await fileUtils.saveLayout(saveAs, layout, 'json');
|
||||
console.log(`Layout saved from template: ${filePath}`);
|
||||
}
|
||||
Reference in New Issue
Block a user