Initial upload with CI/CD workflow
Some checks failed
CI / test (push) Has been cancelled

This commit is contained in:
2026-02-05 19:31:44 +00:00
parent a12aa49a05
commit 8688a56b39

310
src/commands/template.ts Normal file
View 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}`);
}