feat: implement tdd workflow (#1309)

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Ralph Khreish
2025-10-18 16:29:03 +02:00
committed by GitHub
parent b8830d9508
commit ccb87a516a
106 changed files with 25097 additions and 1541 deletions

View File

@@ -0,0 +1,203 @@
/**
* TemplateEngine - Configurable template system for generating text from templates
*
* Supports:
* - Variable substitution using {{variableName}} syntax
* - Custom templates via constructor or setTemplate
* - Template validation with required variables
* - Variable extraction from templates
* - Multiple template storage and retrieval
*/
export interface TemplateValidationResult {
isValid: boolean;
missingVars: string[];
}
export interface TemplateVariables {
[key: string]: string | number | boolean | undefined;
}
export interface TemplateCollection {
[templateName: string]: string;
}
export interface TemplateEngineOptions {
customTemplates?: TemplateCollection;
preservePlaceholders?: boolean;
}
const DEFAULT_TEMPLATES: TemplateCollection = {
commitMessage: `{{type}}{{#scope}}({{scope}}){{/scope}}{{#breaking}}!{{/breaking}}: {{description}}
{{#body}}{{body}}
{{/body}}{{#taskId}}Task: {{taskId}}{{/taskId}}{{#phase}}
Phase: {{phase}}{{/phase}}{{#testsPassing}}
Tests: {{testsPassing}} passing{{#testsFailing}}, {{testsFailing}} failing{{/testsFailing}}{{/testsPassing}}`
};
export class TemplateEngine {
private templates: TemplateCollection;
private preservePlaceholders: boolean;
constructor(
optionsOrTemplates: TemplateEngineOptions | TemplateCollection = {}
) {
// Backward compatibility: support old signature (TemplateCollection) and new signature (TemplateEngineOptions)
const isOptions =
'customTemplates' in optionsOrTemplates ||
'preservePlaceholders' in optionsOrTemplates;
const options: TemplateEngineOptions = isOptions
? (optionsOrTemplates as TemplateEngineOptions)
: { customTemplates: optionsOrTemplates as TemplateCollection };
this.templates = {
...DEFAULT_TEMPLATES,
...(options.customTemplates || {})
};
this.preservePlaceholders = options.preservePlaceholders ?? false;
}
/**
* Render a template with provided variables
*/
render(
templateName: string,
variables: TemplateVariables,
inlineTemplate?: string
): string {
const template =
inlineTemplate !== undefined
? inlineTemplate
: this.templates[templateName];
if (template === undefined) {
throw new Error(`Template "${templateName}" not found`);
}
return this.substituteVariables(template, variables);
}
/**
* Set or update a template
*/
setTemplate(name: string, template: string): void {
this.templates[name] = template;
}
/**
* Get a template by name
*/
getTemplate(name: string): string | undefined {
return this.templates[name];
}
/**
* Check if a template exists
*/
hasTemplate(name: string): boolean {
return name in this.templates;
}
/**
* Validate that a template contains all required variables
*/
validateTemplate(
template: string,
requiredVars: string[]
): TemplateValidationResult {
const templateVars = this.extractVariables(template);
const missingVars = requiredVars.filter(
(varName) => !templateVars.includes(varName)
);
return {
isValid: missingVars.length === 0,
missingVars
};
}
/**
* Extract all variable names from a template
*/
extractVariables(template: string): string[] {
const regex = /\{\{\s*([^}#/\s]+)\s*\}\}/g;
const matches = template.matchAll(regex);
const variables = new Set<string>();
for (const match of matches) {
variables.add(match[1]);
}
return Array.from(variables);
}
/**
* Substitute variables in template
* Supports both {{variable}} and {{#variable}}...{{/variable}} (conditional blocks)
*/
private substituteVariables(
template: string,
variables: TemplateVariables
): string {
let result = template;
// Handle conditional blocks first ({{#var}}...{{/var}})
result = this.processConditionalBlocks(result, variables);
// Handle simple variable substitution ({{var}})
result = result.replace(/\{\{\s*([^}#/\s]+)\s*\}\}/g, (_, varName) => {
const value = variables[varName];
return value !== undefined && value !== null
? String(value)
: this.preservePlaceholders
? `{{${varName}}}`
: '';
});
return result;
}
/**
* Process conditional blocks in template
* {{#variable}}content{{/variable}} - shows content only if variable is truthy
* Processes innermost blocks first to handle nesting
*/
private processConditionalBlocks(
template: string,
variables: TemplateVariables
): string {
let result = template;
let hasChanges = true;
// Keep processing until no more conditional blocks are found
while (hasChanges) {
const before = result;
// Find and replace innermost conditional blocks (non-greedy match)
result = result.replace(
/\{\{#([^}]+)\}\}((?:(?!\{\{#).)*?)\{\{\/\1\}\}/gs,
(_, varName, content) => {
const value = variables[varName.trim()];
// Show content if variable is truthy (not undefined, null, false, or empty string)
if (
value !== undefined &&
value !== null &&
value !== false &&
value !== ''
) {
return content;
}
return '';
}
);
hasChanges = result !== before;
}
return result;
}
}