• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

apowers313 / prompt-tool / 17011080348

16 Aug 2025 05:31PM UTC coverage: 88.947% (-5.7%) from 94.635%
17011080348

push

github

apowers313
ci: fix tests, take 2

1064 of 1231 branches covered (86.43%)

Branch coverage included in aggregate %.

3563 of 3971 relevant lines covered (89.73%)

12.62 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.47
/src/template/template-engine.ts
1
import Handlebars from 'handlebars';
1✔
2
import { TemplateContext } from './template-context.js';
1✔
3
import { registerHelpers } from './helpers/index.js';
1✔
4
import { Prompt } from '../types/prompt.js';
5
import { loadHandlebarsExtensions } from '../utils/handlebars-extensions.js';
1✔
6
import { Config } from '../types/config.js';
7

8
export class TemplateEngine {
1✔
9
  private handlebars: typeof Handlebars;
41✔
10
  private context: TemplateContext;
41✔
11
  private config?: Config;
41✔
12
  private configDir?: string;
41✔
13

14
  constructor(config?: Config, configDir?: string) {
41✔
15
    this.handlebars = Handlebars.create();
41✔
16
    this.context = new TemplateContext();
41✔
17
    this.config = config;
41✔
18
    this.configDir = configDir;
41✔
19
  }
41✔
20

21
  async processTemplate(template: string, prompt: Partial<Prompt>): Promise<string> {
41✔
22
    // Create new context with variable definitions
23
    this.context = new TemplateContext(prompt.variables);
36✔
24

25
    // Create a special Handlebars instance that has access to the context
26
    this.handlebars = Handlebars.create();
36✔
27

28
    // Load Handlebars extensions from config
29
    if (this.config?.handlebarsExtensions) {
36✔
30
      await loadHandlebarsExtensions(this.handlebars, this.config.handlebarsExtensions, this.configDir);
14✔
31
    }
12✔
32

33
    // Register helpers with context
34
    registerHelpers(this.handlebars, this.context);
34✔
35
    
36
    // Pre-process template to extract and protect raw blocks
37
    const rawBlocks: Array<{ placeholder: string; content: string }> = [];
34✔
38
    let rawIndex = 0;
34✔
39
    
40
    // Extract content from {{#raw}}...{{/raw}} blocks before Handlebars processes them
41
    template = template.replace(/{{#raw}}([\s\S]*?){{\/raw}}/g, (match, content) => {
34✔
42
      const placeholder = `__RAW_BLOCK_${rawIndex++}__`;
3✔
43
      rawBlocks.push({ placeholder, content });
3✔
44
      return placeholder;
3✔
45
    });
34✔
46

47
    // Phase 1: Replace variable references with temporary placeholders
48
    // to prevent them from being processed before values are available
49
    const varPattern = /\{\{([^{}\s]+)\}\}/g;
34✔
50
    const inputHelperPattern =
34✔
51
      /\{\{(input|select|multiselect|confirm|editor|password)\s+[^}]+\}\}/g;
34✔
52

53
    // Store variable references to restore later
54
    const variableRefs: Array<{ placeholder: string; variable: string }> = [];
34✔
55
    let tempTemplate = template;
34✔
56
    let varIndex = 0;
34✔
57

58
    // Replace non-helper variable references with placeholders
59
    tempTemplate = tempTemplate.replace(varPattern, (match, varName) => {
34✔
60
      // Check if this is a helper call
61
      if (match.match(inputHelperPattern)) {
5!
62
        return match; // Keep helper calls as-is
×
63
      }
×
64
      const placeholder = `__VAR_${varIndex++}__`;
5✔
65
      variableRefs.push({ placeholder, variable: varName });
5✔
66
      return placeholder;
5✔
67
    });
34✔
68

69
    // Phase 2: Process helpers with the temporary template
70
    const compiled = this.handlebars.compile(tempTemplate, { noEscape: true });
34✔
71
    let result = compiled({});
34✔
72

73
    // Process async operations (input helpers)
74
    result = await this.context.processAsyncOperations(result);
34✔
75

76
    // Phase 3: Restore variable references
77
    for (const ref of variableRefs) {
36✔
78
      result = result.replace(ref.placeholder, `{{${ref.variable}}}`);
5✔
79
    }
5✔
80

81
    // Phase 4: Final compilation with all values available
82
    const finalCompiled = this.handlebars.compile(result, { noEscape: true });
31✔
83
    const contextData: Record<string, unknown> = {};
31✔
84

85
    // Add all collected values to context data
86
    for (const [key, value] of this.context.getAllValues()) {
33✔
87
      contextData[key] = value;
22✔
88
    }
22✔
89

90
    let finalResult = finalCompiled(contextData);
31✔
91
    
92
    // Restore raw blocks
93
    for (const block of rawBlocks) {
35✔
94
      finalResult = finalResult.replace(block.placeholder, block.content);
3✔
95
    }
3✔
96
    
97
    // Restore escaped Handlebars syntax from user input
98
    finalResult = finalResult.replace(/__ESCAPED_OPEN__/g, '{{').replace(/__ESCAPED_CLOSE__/g, '}}');
31✔
99
    
100
    return finalResult;
31✔
101
  }
36✔
102

103
  getContext(): TemplateContext {
41✔
104
    return this.context;
3✔
105
  }
3✔
106
}
41✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc