@prompd/cli 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/README.md +162 -0
  2. package/bin/prompd.js +23 -0
  3. package/dist/commands/cache.d.ts +3 -0
  4. package/dist/commands/cache.d.ts.map +1 -0
  5. package/dist/commands/cache.js +199 -0
  6. package/dist/commands/cache.js.map +1 -0
  7. package/dist/commands/compile.d.ts +9 -0
  8. package/dist/commands/compile.d.ts.map +1 -0
  9. package/dist/commands/compile.js +104 -0
  10. package/dist/commands/compile.js.map +1 -0
  11. package/dist/commands/config.d.ts +7 -0
  12. package/dist/commands/config.d.ts.map +1 -0
  13. package/dist/commands/config.js +212 -0
  14. package/dist/commands/config.js.map +1 -0
  15. package/dist/commands/create.d.ts +3 -0
  16. package/dist/commands/create.d.ts.map +1 -0
  17. package/dist/commands/create.js +183 -0
  18. package/dist/commands/create.js.map +1 -0
  19. package/dist/commands/deps.d.ts +3 -0
  20. package/dist/commands/deps.d.ts.map +1 -0
  21. package/dist/commands/deps.js +192 -0
  22. package/dist/commands/deps.js.map +1 -0
  23. package/dist/commands/explain.d.ts +3 -0
  24. package/dist/commands/explain.d.ts.map +1 -0
  25. package/dist/commands/explain.js +227 -0
  26. package/dist/commands/explain.js.map +1 -0
  27. package/dist/commands/git.d.ts +3 -0
  28. package/dist/commands/git.d.ts.map +1 -0
  29. package/dist/commands/git.js +306 -0
  30. package/dist/commands/git.js.map +1 -0
  31. package/dist/commands/init.d.ts +3 -0
  32. package/dist/commands/init.d.ts.map +1 -0
  33. package/dist/commands/init.js +177 -0
  34. package/dist/commands/init.js.map +1 -0
  35. package/dist/commands/list.d.ts +3 -0
  36. package/dist/commands/list.d.ts.map +1 -0
  37. package/dist/commands/list.js +126 -0
  38. package/dist/commands/list.js.map +1 -0
  39. package/dist/commands/mcp.d.ts +3 -0
  40. package/dist/commands/mcp.d.ts.map +1 -0
  41. package/dist/commands/mcp.js +326 -0
  42. package/dist/commands/mcp.js.map +1 -0
  43. package/dist/commands/namespace.d.ts +3 -0
  44. package/dist/commands/namespace.d.ts.map +1 -0
  45. package/dist/commands/namespace.js +113 -0
  46. package/dist/commands/namespace.js.map +1 -0
  47. package/dist/commands/package.d.ts +23 -0
  48. package/dist/commands/package.d.ts.map +1 -0
  49. package/dist/commands/package.js +746 -0
  50. package/dist/commands/package.js.map +1 -0
  51. package/dist/commands/provider.d.ts +3 -0
  52. package/dist/commands/provider.d.ts.map +1 -0
  53. package/dist/commands/provider.js +285 -0
  54. package/dist/commands/provider.js.map +1 -0
  55. package/dist/commands/registry.d.ts +9 -0
  56. package/dist/commands/registry.d.ts.map +1 -0
  57. package/dist/commands/registry.js +361 -0
  58. package/dist/commands/registry.js.map +1 -0
  59. package/dist/commands/run.d.ts +3 -0
  60. package/dist/commands/run.d.ts.map +1 -0
  61. package/dist/commands/run.js +157 -0
  62. package/dist/commands/run.js.map +1 -0
  63. package/dist/commands/show.d.ts +3 -0
  64. package/dist/commands/show.d.ts.map +1 -0
  65. package/dist/commands/show.js +90 -0
  66. package/dist/commands/show.js.map +1 -0
  67. package/dist/commands/uninstall.d.ts +3 -0
  68. package/dist/commands/uninstall.d.ts.map +1 -0
  69. package/dist/commands/uninstall.js +95 -0
  70. package/dist/commands/uninstall.js.map +1 -0
  71. package/dist/commands/validate.d.ts +3 -0
  72. package/dist/commands/validate.d.ts.map +1 -0
  73. package/dist/commands/validate.js +57 -0
  74. package/dist/commands/validate.js.map +1 -0
  75. package/dist/commands/version.d.ts +3 -0
  76. package/dist/commands/version.d.ts.map +1 -0
  77. package/dist/commands/version.js +166 -0
  78. package/dist/commands/version.js.map +1 -0
  79. package/dist/index.d.ts +5 -0
  80. package/dist/index.d.ts.map +1 -0
  81. package/dist/index.js +388 -0
  82. package/dist/index.js.map +1 -0
  83. package/dist/lib/auth.d.ts +164 -0
  84. package/dist/lib/auth.d.ts.map +1 -0
  85. package/dist/lib/auth.js +388 -0
  86. package/dist/lib/auth.js.map +1 -0
  87. package/dist/lib/compiler/file-system.d.ts +178 -0
  88. package/dist/lib/compiler/file-system.d.ts.map +1 -0
  89. package/dist/lib/compiler/file-system.js +440 -0
  90. package/dist/lib/compiler/file-system.js.map +1 -0
  91. package/dist/lib/compiler/formatters/anthropic.d.ts +21 -0
  92. package/dist/lib/compiler/formatters/anthropic.d.ts.map +1 -0
  93. package/dist/lib/compiler/formatters/anthropic.js +95 -0
  94. package/dist/lib/compiler/formatters/anthropic.js.map +1 -0
  95. package/dist/lib/compiler/formatters/markdown.d.ts +17 -0
  96. package/dist/lib/compiler/formatters/markdown.d.ts.map +1 -0
  97. package/dist/lib/compiler/formatters/markdown.js +114 -0
  98. package/dist/lib/compiler/formatters/markdown.js.map +1 -0
  99. package/dist/lib/compiler/formatters/openai.d.ts +21 -0
  100. package/dist/lib/compiler/formatters/openai.d.ts.map +1 -0
  101. package/dist/lib/compiler/formatters/openai.js +98 -0
  102. package/dist/lib/compiler/formatters/openai.js.map +1 -0
  103. package/dist/lib/compiler/index.d.ts +56 -0
  104. package/dist/lib/compiler/index.d.ts.map +1 -0
  105. package/dist/lib/compiler/index.js +165 -0
  106. package/dist/lib/compiler/index.js.map +1 -0
  107. package/dist/lib/compiler/language-map.d.ts +31 -0
  108. package/dist/lib/compiler/language-map.d.ts.map +1 -0
  109. package/dist/lib/compiler/language-map.js +156 -0
  110. package/dist/lib/compiler/language-map.js.map +1 -0
  111. package/dist/lib/compiler/package-resolver.d.ts +68 -0
  112. package/dist/lib/compiler/package-resolver.d.ts.map +1 -0
  113. package/dist/lib/compiler/package-resolver.js +254 -0
  114. package/dist/lib/compiler/package-resolver.js.map +1 -0
  115. package/dist/lib/compiler/pipeline.d.ts +53 -0
  116. package/dist/lib/compiler/pipeline.d.ts.map +1 -0
  117. package/dist/lib/compiler/pipeline.js +209 -0
  118. package/dist/lib/compiler/pipeline.js.map +1 -0
  119. package/dist/lib/compiler/prompd-loader.d.ts +108 -0
  120. package/dist/lib/compiler/prompd-loader.d.ts.map +1 -0
  121. package/dist/lib/compiler/prompd-loader.js +270 -0
  122. package/dist/lib/compiler/prompd-loader.js.map +1 -0
  123. package/dist/lib/compiler/section-override.d.ts +40 -0
  124. package/dist/lib/compiler/section-override.d.ts.map +1 -0
  125. package/dist/lib/compiler/section-override.js +296 -0
  126. package/dist/lib/compiler/section-override.js.map +1 -0
  127. package/dist/lib/compiler/stages/assets.d.ts +71 -0
  128. package/dist/lib/compiler/stages/assets.d.ts.map +1 -0
  129. package/dist/lib/compiler/stages/assets.js +456 -0
  130. package/dist/lib/compiler/stages/assets.js.map +1 -0
  131. package/dist/lib/compiler/stages/codegen.d.ts +17 -0
  132. package/dist/lib/compiler/stages/codegen.d.ts.map +1 -0
  133. package/dist/lib/compiler/stages/codegen.js +64 -0
  134. package/dist/lib/compiler/stages/codegen.js.map +1 -0
  135. package/dist/lib/compiler/stages/dependency.d.ts +38 -0
  136. package/dist/lib/compiler/stages/dependency.d.ts.map +1 -0
  137. package/dist/lib/compiler/stages/dependency.js +307 -0
  138. package/dist/lib/compiler/stages/dependency.js.map +1 -0
  139. package/dist/lib/compiler/stages/lexical.d.ts +19 -0
  140. package/dist/lib/compiler/stages/lexical.d.ts.map +1 -0
  141. package/dist/lib/compiler/stages/lexical.js +92 -0
  142. package/dist/lib/compiler/stages/lexical.js.map +1 -0
  143. package/dist/lib/compiler/stages/semantic.d.ts +20 -0
  144. package/dist/lib/compiler/stages/semantic.d.ts.map +1 -0
  145. package/dist/lib/compiler/stages/semantic.js +166 -0
  146. package/dist/lib/compiler/stages/semantic.js.map +1 -0
  147. package/dist/lib/compiler/stages/template.d.ts +94 -0
  148. package/dist/lib/compiler/stages/template.d.ts.map +1 -0
  149. package/dist/lib/compiler/stages/template.js +1044 -0
  150. package/dist/lib/compiler/stages/template.js.map +1 -0
  151. package/dist/lib/compiler/types.d.ts +200 -0
  152. package/dist/lib/compiler/types.d.ts.map +1 -0
  153. package/dist/lib/compiler/types.js +137 -0
  154. package/dist/lib/compiler/types.js.map +1 -0
  155. package/dist/lib/config.d.ts +29 -0
  156. package/dist/lib/config.d.ts.map +1 -0
  157. package/dist/lib/config.js +375 -0
  158. package/dist/lib/config.js.map +1 -0
  159. package/dist/lib/errors.d.ts +19 -0
  160. package/dist/lib/errors.d.ts.map +1 -0
  161. package/dist/lib/errors.js +47 -0
  162. package/dist/lib/errors.js.map +1 -0
  163. package/dist/lib/executor.d.ts +18 -0
  164. package/dist/lib/executor.d.ts.map +1 -0
  165. package/dist/lib/executor.js +372 -0
  166. package/dist/lib/executor.js.map +1 -0
  167. package/dist/lib/git.d.ts +74 -0
  168. package/dist/lib/git.d.ts.map +1 -0
  169. package/dist/lib/git.js +254 -0
  170. package/dist/lib/git.js.map +1 -0
  171. package/dist/lib/index.d.ts +43 -0
  172. package/dist/lib/index.d.ts.map +1 -0
  173. package/dist/lib/index.js +108 -0
  174. package/dist/lib/index.js.map +1 -0
  175. package/dist/lib/mcp.d.ts +42 -0
  176. package/dist/lib/mcp.d.ts.map +1 -0
  177. package/dist/lib/mcp.js +477 -0
  178. package/dist/lib/mcp.js.map +1 -0
  179. package/dist/lib/model-updater.d.ts +51 -0
  180. package/dist/lib/model-updater.d.ts.map +1 -0
  181. package/dist/lib/model-updater.js +275 -0
  182. package/dist/lib/model-updater.js.map +1 -0
  183. package/dist/lib/parser.d.ts +9 -0
  184. package/dist/lib/parser.d.ts.map +1 -0
  185. package/dist/lib/parser.js +197 -0
  186. package/dist/lib/parser.js.map +1 -0
  187. package/dist/lib/registry.d.ts +183 -0
  188. package/dist/lib/registry.d.ts.map +1 -0
  189. package/dist/lib/registry.js +786 -0
  190. package/dist/lib/registry.js.map +1 -0
  191. package/dist/lib/rpc-server.d.ts +78 -0
  192. package/dist/lib/rpc-server.d.ts.map +1 -0
  193. package/dist/lib/rpc-server.js +404 -0
  194. package/dist/lib/rpc-server.js.map +1 -0
  195. package/dist/lib/security.d.ts +120 -0
  196. package/dist/lib/security.d.ts.map +1 -0
  197. package/dist/lib/security.js +478 -0
  198. package/dist/lib/security.js.map +1 -0
  199. package/dist/lib/validation.d.ts +106 -0
  200. package/dist/lib/validation.d.ts.map +1 -0
  201. package/dist/lib/validation.js +398 -0
  202. package/dist/lib/validation.js.map +1 -0
  203. package/dist/lib/version.d.ts +29 -0
  204. package/dist/lib/version.d.ts.map +1 -0
  205. package/dist/lib/version.js +202 -0
  206. package/dist/lib/version.js.map +1 -0
  207. package/dist/lib/workflow-engine.d.ts +161 -0
  208. package/dist/lib/workflow-engine.d.ts.map +1 -0
  209. package/dist/lib/workflow-engine.js +422 -0
  210. package/dist/lib/workflow-engine.js.map +1 -0
  211. package/dist/lib/workflow.d.ts +102 -0
  212. package/dist/lib/workflow.d.ts.map +1 -0
  213. package/dist/lib/workflow.js +228 -0
  214. package/dist/lib/workflow.js.map +1 -0
  215. package/dist/server.d.ts +8 -0
  216. package/dist/server.d.ts.map +1 -0
  217. package/dist/server.js +134 -0
  218. package/dist/server.js.map +1 -0
  219. package/dist/types/index.d.ts +116 -0
  220. package/dist/types/index.d.ts.map +1 -0
  221. package/dist/types/index.js +144 -0
  222. package/dist/types/index.js.map +1 -0
  223. package/package.json +104 -0
@@ -0,0 +1,1044 @@
1
+ "use strict";
2
+ /**
3
+ * Template Processing Stage
4
+ *
5
+ * Processes Jinja2/Nunjucks templates, package references, and section overrides.
6
+ * This is the most complex stage, handling:
7
+ * - Nunjucks template rendering with custom filters (fromcsv, fromjson, tojson, lines)
8
+ * - Package reference resolution and content injection
9
+ * - Inheritance processing with section-aware merging
10
+ * - Enhanced variable substitution with nested property access
11
+ */
12
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ var desc = Object.getOwnPropertyDescriptor(m, k);
15
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
16
+ desc = { enumerable: true, get: function() { return m[k]; } };
17
+ }
18
+ Object.defineProperty(o, k2, desc);
19
+ }) : (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ o[k2] = m[k];
22
+ }));
23
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
24
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
25
+ }) : function(o, v) {
26
+ o["default"] = v;
27
+ });
28
+ var __importStar = (this && this.__importStar) || (function () {
29
+ var ownKeys = function(o) {
30
+ ownKeys = Object.getOwnPropertyNames || function (o) {
31
+ var ar = [];
32
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
33
+ return ar;
34
+ };
35
+ return ownKeys(o);
36
+ };
37
+ return function (mod) {
38
+ if (mod && mod.__esModule) return mod;
39
+ var result = {};
40
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
41
+ __setModuleDefault(result, mod);
42
+ return result;
43
+ };
44
+ })();
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ exports.TemplateProcessingStage = void 0;
47
+ const nunjucks = __importStar(require("nunjucks"));
48
+ const path = __importStar(require("path"));
49
+ const parser_1 = require("../../parser");
50
+ const section_override_1 = require("../section-override");
51
+ const package_resolver_1 = require("../package-resolver");
52
+ const language_map_1 = require("../language-map");
53
+ const prompd_loader_1 = require("../prompd-loader");
54
+ class TemplateProcessingStage {
55
+ constructor() {
56
+ this.sectionProcessor = new section_override_1.SectionOverrideProcessor();
57
+ // Configure Nunjucks to use double braces for variables (standard Jinja2/Nunjucks syntax)
58
+ this.nunjucksEnv = new nunjucks.Environment(null, {
59
+ autoescape: false, // Don't escape HTML - we're doing markdown
60
+ throwOnUndefined: false, // Gracefully handle missing variables
61
+ trimBlocks: true,
62
+ lstripBlocks: true,
63
+ tags: {
64
+ blockStart: '{%',
65
+ blockEnd: '%}',
66
+ variableStart: '{{',
67
+ variableEnd: '}}',
68
+ commentStart: '{#',
69
+ commentEnd: '#}'
70
+ }
71
+ });
72
+ // Register custom filters
73
+ this.registerFilters();
74
+ }
75
+ /**
76
+ * Register custom Jinja2/Nunjucks filters for data transformation.
77
+ */
78
+ registerFilters() {
79
+ // fromcsv - Parse CSV string into array of objects
80
+ this.nunjucksEnv.addFilter('fromcsv', (csvString) => {
81
+ if (!csvString || typeof csvString !== 'string') {
82
+ return [];
83
+ }
84
+ return this.parseCsv(csvString);
85
+ });
86
+ // fromjson - Parse JSON string into object/array
87
+ this.nunjucksEnv.addFilter('fromjson', (jsonString) => {
88
+ if (!jsonString || typeof jsonString !== 'string') {
89
+ return null;
90
+ }
91
+ try {
92
+ return JSON.parse(jsonString);
93
+ }
94
+ catch {
95
+ return null;
96
+ }
97
+ });
98
+ // tojson - Convert object to JSON string
99
+ this.nunjucksEnv.addFilter('tojson', (obj, indent) => {
100
+ try {
101
+ return JSON.stringify(obj, null, indent);
102
+ }
103
+ catch {
104
+ return '{}';
105
+ }
106
+ });
107
+ // lines - Split string into array of lines
108
+ this.nunjucksEnv.addFilter('lines', (str) => {
109
+ if (!str || typeof str !== 'string') {
110
+ return [];
111
+ }
112
+ return str.split(/\r?\n/);
113
+ });
114
+ // dedent - Remove common leading whitespace from text
115
+ this.nunjucksEnv.addFilter('dedent', (str) => {
116
+ if (!str || typeof str !== 'string') {
117
+ return '';
118
+ }
119
+ const lines = str.split(/\r?\n/);
120
+ // Find minimum indentation (ignoring empty lines)
121
+ let minIndent = Infinity;
122
+ for (const line of lines) {
123
+ if (line.trim().length === 0)
124
+ continue;
125
+ const match = line.match(/^(\s*)/);
126
+ if (match && match[1].length < minIndent) {
127
+ minIndent = match[1].length;
128
+ }
129
+ }
130
+ if (minIndent === Infinity)
131
+ minIndent = 0;
132
+ // Remove the common indent from all lines
133
+ return lines.map(line => line.slice(minIndent)).join('\n');
134
+ });
135
+ // truncate - Truncate string with ellipsis
136
+ this.nunjucksEnv.addFilter('truncate', (str, length = 80, suffix = '...') => {
137
+ if (!str || typeof str !== 'string') {
138
+ return '';
139
+ }
140
+ if (str.length <= length) {
141
+ return str;
142
+ }
143
+ return str.slice(0, length - suffix.length) + suffix;
144
+ });
145
+ // codeblock - Wrap content in fenced code block
146
+ this.nunjucksEnv.addFilter('codeblock', (str, language = '') => {
147
+ if (!str || typeof str !== 'string') {
148
+ return '';
149
+ }
150
+ return '```' + language + '\n' + str + '\n```';
151
+ });
152
+ // unique - Remove duplicate items from array
153
+ this.nunjucksEnv.addFilter('unique', (arr) => {
154
+ if (!Array.isArray(arr)) {
155
+ return [];
156
+ }
157
+ return [...new Set(arr)];
158
+ });
159
+ // pluck - Extract single field from array of objects
160
+ this.nunjucksEnv.addFilter('pluck', (arr, field) => {
161
+ if (!Array.isArray(arr)) {
162
+ return [];
163
+ }
164
+ return arr.map(item => item?.[field]).filter(v => v !== undefined);
165
+ });
166
+ // where - Filter objects by field value
167
+ this.nunjucksEnv.addFilter('where', (arr, field, value) => {
168
+ if (!Array.isArray(arr)) {
169
+ return [];
170
+ }
171
+ return arr.filter(item => item?.[field] === value);
172
+ });
173
+ // groupby - Group array items by field value
174
+ this.nunjucksEnv.addFilter('groupby', (arr, field) => {
175
+ if (!Array.isArray(arr)) {
176
+ return {};
177
+ }
178
+ const result = {};
179
+ for (const item of arr) {
180
+ const key = String(item?.[field] ?? 'undefined');
181
+ if (!result[key]) {
182
+ result[key] = [];
183
+ }
184
+ result[key].push(item);
185
+ }
186
+ return result;
187
+ });
188
+ // shuffle - Randomize array order
189
+ this.nunjucksEnv.addFilter('shuffle', (arr) => {
190
+ if (!Array.isArray(arr)) {
191
+ return [];
192
+ }
193
+ const shuffled = [...arr];
194
+ for (let i = shuffled.length - 1; i > 0; i--) {
195
+ const j = Math.floor(Math.random() * (i + 1));
196
+ [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
197
+ }
198
+ return shuffled;
199
+ });
200
+ // sample - Pick random N items from array
201
+ this.nunjucksEnv.addFilter('sample', (arr, count = 1) => {
202
+ if (!Array.isArray(arr)) {
203
+ return [];
204
+ }
205
+ const shuffled = [...arr];
206
+ for (let i = shuffled.length - 1; i > 0; i--) {
207
+ const j = Math.floor(Math.random() * (i + 1));
208
+ [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
209
+ }
210
+ return shuffled.slice(0, Math.min(count, shuffled.length));
211
+ });
212
+ // wordwrap - Wrap text at specified width
213
+ this.nunjucksEnv.addFilter('wordwrap', (str, width = 80) => {
214
+ if (!str || typeof str !== 'string') {
215
+ return '';
216
+ }
217
+ const words = str.split(/\s+/);
218
+ const lines = [];
219
+ let currentLine = '';
220
+ for (const word of words) {
221
+ if (currentLine.length + word.length + 1 <= width) {
222
+ currentLine += (currentLine ? ' ' : '') + word;
223
+ }
224
+ else {
225
+ if (currentLine)
226
+ lines.push(currentLine);
227
+ currentLine = word;
228
+ }
229
+ }
230
+ if (currentLine)
231
+ lines.push(currentLine);
232
+ return lines.join('\n');
233
+ });
234
+ // bulletlist - Convert array/lines to bullet list
235
+ this.nunjucksEnv.addFilter('bulletlist', (input) => {
236
+ const items = Array.isArray(input) ? input : String(input).split(/\r?\n/);
237
+ return items.filter(item => String(item).trim()).map(item => `- ${item}`).join('\n');
238
+ });
239
+ // numberedlist - Convert array/lines to numbered list
240
+ this.nunjucksEnv.addFilter('numberedlist', (input) => {
241
+ const items = Array.isArray(input) ? input : String(input).split(/\r?\n/);
242
+ return items.filter(item => String(item).trim()).map((item, i) => `${i + 1}. ${item}`).join('\n');
243
+ });
244
+ }
245
+ /**
246
+ * Parse CSV string into array of record objects.
247
+ */
248
+ parseCsv(csvString) {
249
+ const lines = csvString.trim().split(/\r?\n/);
250
+ if (lines.length === 0) {
251
+ return [];
252
+ }
253
+ // Parse header row
254
+ const headers = this.parseCsvLine(lines[0]);
255
+ // Parse data rows
256
+ const records = [];
257
+ for (let i = 1; i < lines.length; i++) {
258
+ const line = lines[i].trim();
259
+ if (!line)
260
+ continue; // Skip empty lines
261
+ const values = this.parseCsvLine(line);
262
+ const record = {};
263
+ for (let j = 0; j < headers.length; j++) {
264
+ const header = headers[j].trim();
265
+ const value = values[j]?.trim() ?? '';
266
+ record[header] = value;
267
+ }
268
+ records.push(record);
269
+ }
270
+ return records;
271
+ }
272
+ /**
273
+ * Parse a single CSV line, handling quoted values.
274
+ */
275
+ parseCsvLine(line) {
276
+ const result = [];
277
+ let current = '';
278
+ let inQuotes = false;
279
+ for (let i = 0; i < line.length; i++) {
280
+ const char = line[i];
281
+ if (char === '"') {
282
+ if (inQuotes && line[i + 1] === '"') {
283
+ // Escaped quote
284
+ current += '"';
285
+ i++;
286
+ }
287
+ else {
288
+ // Toggle quote mode
289
+ inQuotes = !inQuotes;
290
+ }
291
+ }
292
+ else if (char === ',' && !inQuotes) {
293
+ result.push(current);
294
+ current = '';
295
+ }
296
+ else {
297
+ current += char;
298
+ }
299
+ }
300
+ result.push(current);
301
+ return result;
302
+ }
303
+ async process(context) {
304
+ if (!context.content) {
305
+ return;
306
+ }
307
+ let content = context.content;
308
+ // Step 1: Process package references with prefixes (e.g., @security/prompts/audit)
309
+ if (context.dependencies.imports) {
310
+ content = await this.processPackageReferences(context, content);
311
+ }
312
+ // Step 2: Process inheritance with section-aware override support
313
+ if (context.dependencies.inherits) {
314
+ content = await this.processInheritance(context, content);
315
+ }
316
+ // Step 3: Process standalone overrides (without inheritance)
317
+ if (context.metadata?.override && !context.dependencies.inherits) {
318
+ content = await this.processStandaloneOverrides(context, content);
319
+ }
320
+ // Step 4: Filter code blocks based on context file types
321
+ // If context files are attached, only keep code blocks that match those file extensions
322
+ if (context.metadata?.context) {
323
+ content = this.filterCodeBlocksByContext(context, content);
324
+ }
325
+ // Step 5: Process templates with Jinja2/Nunjucks
326
+ content = await this.processTemplate(context, content);
327
+ context.content = content;
328
+ }
329
+ /**
330
+ * Process package references (e.g., @prefix/path/to/file).
331
+ */
332
+ async processPackageReferences(context, content) {
333
+ if (!context.dependencies.imports) {
334
+ return content;
335
+ }
336
+ for (const [packageRef, packageInfo] of Object.entries(context.dependencies.imports)) {
337
+ if (!packageInfo.prefix || !packageInfo.path) {
338
+ continue;
339
+ }
340
+ const prefix = packageInfo.prefix;
341
+ const packagePath = packageInfo.path;
342
+ // Replace references like @security/prompts/audit with actual content
343
+ // Pattern: @prefix/path/to/resource
344
+ const pattern = new RegExp(`@${this.escapeRegex(prefix)}/([^\\s]+)`, 'g');
345
+ // Find all matches first
346
+ const matches = [];
347
+ let match;
348
+ while ((match = pattern.exec(content)) !== null) {
349
+ matches.push({
350
+ match: match[0],
351
+ resourcePath: match[1],
352
+ index: match.index
353
+ });
354
+ }
355
+ // Replace in reverse order to preserve indices
356
+ for (let i = matches.length - 1; i >= 0; i--) {
357
+ const m = matches[i];
358
+ const replacement = await this.loadPackageResource(context, packagePath, m.resourcePath, prefix);
359
+ content = content.substring(0, m.index) + replacement + content.substring(m.index + m.match.length);
360
+ }
361
+ }
362
+ return content;
363
+ }
364
+ /**
365
+ * Load a resource from a package.
366
+ */
367
+ async loadPackageResource(context, packagePath, resourcePath, prefix) {
368
+ try {
369
+ const fs = context.fileSystem;
370
+ // Try different extensions
371
+ const possibleExtensions = ['', '.prmd', '.md', '.txt'];
372
+ for (const ext of possibleExtensions) {
373
+ const filePath = (0, package_resolver_1.resolvePackageFile)(packagePath, resourcePath + ext);
374
+ if (await fs.exists(filePath)) {
375
+ const contentData = await fs.readFile(filePath);
376
+ // If it's a .prmd file, parse and extract content
377
+ if (filePath.endsWith('.prmd')) {
378
+ const parser = new parser_1.PrompdParser();
379
+ try {
380
+ const parsed = parser.parseContent(contentData);
381
+ // Return just the content part, not metadata
382
+ return parsed.content || `# Content from @${prefix}/${resourcePath}`;
383
+ }
384
+ catch {
385
+ // If parsing fails, return raw content
386
+ return contentData;
387
+ }
388
+ }
389
+ else {
390
+ // For other files, return as-is
391
+ return contentData;
392
+ }
393
+ }
394
+ }
395
+ // File not found
396
+ if (context.verbose) {
397
+ console.log(`Warning: Could not find @${prefix}/${resourcePath} in ${packagePath}`);
398
+ }
399
+ return `[Not found: @${prefix}/${resourcePath}]`;
400
+ }
401
+ catch (error) {
402
+ if (context.verbose) {
403
+ console.log(`Warning: Failed to load @${prefix}/${resourcePath}: ${error}`);
404
+ }
405
+ return `[Error loading @${prefix}/${resourcePath}]`;
406
+ }
407
+ }
408
+ /**
409
+ * Process inheritance with section-aware merging.
410
+ */
411
+ async processInheritance(context, content) {
412
+ const parentPath = context.dependencies.inherits;
413
+ if (!parentPath) {
414
+ return content;
415
+ }
416
+ try {
417
+ const fs = context.fileSystem;
418
+ const parser = new parser_1.PrompdParser();
419
+ let parentFile = null;
420
+ // Check if it's a direct file path
421
+ if (parentPath.endsWith('.prmd') && await fs.exists(parentPath)) {
422
+ parentFile = parentPath;
423
+ }
424
+ else if (await fs.exists(parentPath) && await fs.isDirectory(parentPath)) {
425
+ // Package directory - find main .prmd file
426
+ const files = (await fs.readdir(parentPath)).filter((f) => f.endsWith('.prmd'));
427
+ if (files.length > 0) {
428
+ // Look for main.prmd first
429
+ const mainFile = files.find((f) => f === 'main.prmd');
430
+ parentFile = fs.join(parentPath, mainFile || files[0]);
431
+ }
432
+ }
433
+ if (!parentFile) {
434
+ // Find location of 'inherits:' in source for accurate error positioning
435
+ const location = context.findLocation(/inherits:/);
436
+ context.addDiagnostic({
437
+ message: `Could not find inherited file: ${parentPath}`,
438
+ severity: 'error',
439
+ source: 'template',
440
+ code: 'INHERITED_FILE_NOT_FOUND',
441
+ ...location
442
+ });
443
+ return content;
444
+ }
445
+ // Parse parent file - read content from file system
446
+ const parentFileContent = await fs.readFile(parentFile);
447
+ const parentData = parser.parseContent(parentFileContent);
448
+ // Get overrides from child metadata
449
+ const overrides = context.metadata?.override || {};
450
+ // Process content with section-aware merging
451
+ if (parentData.content || content) {
452
+ if (Object.keys(overrides).length > 0) {
453
+ // Section-aware override processing
454
+ try {
455
+ // Extract sections from parent and child
456
+ const parentSections = parentData.content
457
+ ? this.sectionProcessor.extractSections(parentData.content)
458
+ : new Map();
459
+ const childSections = content
460
+ ? this.sectionProcessor.extractSections(content)
461
+ : new Map();
462
+ // Determine base directory for resolving override files
463
+ const baseDir = context.fileSystem.dirname(context.sourceFile);
464
+ // Apply overrides and merge content
465
+ const mergedContent = await this.sectionProcessor.applyOverrides(parentSections, childSections, overrides, baseDir, context.verbose, context.fileSystem);
466
+ content = mergedContent;
467
+ if (context.verbose) {
468
+ console.log(`✓ Applied ${Object.keys(overrides).length} section overrides from parent: ${parentPath}`);
469
+ }
470
+ }
471
+ catch (error) {
472
+ // Fallback to simple concatenation on error
473
+ const errorMessage = error instanceof Error ? error.message : String(error);
474
+ context.addWarning(`Section override processing failed, using simple inheritance: ${errorMessage}`);
475
+ if (parentData.content) {
476
+ content = content ? `${parentData.content}\n\n${content}` : parentData.content;
477
+ }
478
+ }
479
+ }
480
+ else {
481
+ // No overrides - use simple concatenation (backward compatibility)
482
+ if (parentData.content) {
483
+ content = content ? `${parentData.content}\n\n${content}` : parentData.content;
484
+ }
485
+ }
486
+ }
487
+ // Merge parent parameters (child parameters override)
488
+ if (parentData.metadata?.parameters && context.metadata) {
489
+ for (const parentParam of parentData.metadata.parameters) {
490
+ // Only add parent parameter if not defined in child
491
+ const paramExists = context.metadata.parameters?.some(childParam => childParam.name === parentParam.name);
492
+ if (!paramExists) {
493
+ if (!context.metadata.parameters) {
494
+ context.metadata.parameters = [];
495
+ }
496
+ context.metadata.parameters.push(parentParam);
497
+ }
498
+ }
499
+ }
500
+ if (context.verbose) {
501
+ console.log(`✓ Template inherits from: ${parentPath}`);
502
+ }
503
+ }
504
+ catch (error) {
505
+ const errorMessage = error instanceof Error ? error.message : String(error);
506
+ context.addWarning(`Failed to process inheritance from ${parentPath}: ${errorMessage}`);
507
+ }
508
+ return content;
509
+ }
510
+ /**
511
+ * Process standalone overrides (without inheritance).
512
+ */
513
+ async processStandaloneOverrides(context, content) {
514
+ const overrides = context.metadata?.override;
515
+ if (!overrides) {
516
+ return content;
517
+ }
518
+ if (context.verbose) {
519
+ console.log(`Processing ${Object.keys(overrides).length} standalone section overrides...`);
520
+ }
521
+ try {
522
+ // Extract sections from current content
523
+ const currentSections = this.sectionProcessor.extractSections(content);
524
+ // Determine base directory for resolving override files
525
+ const baseDir = context.fileSystem.dirname(context.sourceFile);
526
+ // Apply each override to replace sections in current content
527
+ for (const [sectionId, overridePath] of Object.entries(overrides)) {
528
+ if (currentSections.has(sectionId)) {
529
+ if (overridePath === null) {
530
+ // Remove section
531
+ currentSections.delete(sectionId);
532
+ if (context.verbose) {
533
+ console.log(` - Removing section '${sectionId}'`);
534
+ }
535
+ }
536
+ else if (typeof overridePath === 'string') {
537
+ // Replace section content
538
+ try {
539
+ const overrideContent = await this.sectionProcessor.loadOverrideContent(overridePath, baseDir, context.fileSystem);
540
+ // Update section with override content
541
+ const originalSection = currentSections.get(sectionId);
542
+ currentSections.set(sectionId, {
543
+ ...originalSection,
544
+ content: overrideContent
545
+ });
546
+ if (context.verbose) {
547
+ console.log(` - Replacing section '${sectionId}' with content from ${overridePath}`);
548
+ }
549
+ }
550
+ catch (error) {
551
+ const errorMessage = error instanceof Error ? error.message : String(error);
552
+ context.addWarning(`Failed to apply override for section '${sectionId}': ${errorMessage}`);
553
+ }
554
+ }
555
+ }
556
+ else {
557
+ context.addWarning(`Override section '${sectionId}' not found in current content`);
558
+ if (context.verbose) {
559
+ const available = Array.from(currentSections.keys()).sort().join(', ');
560
+ console.log(`Warning: Override section '${sectionId}' not found. Available: ${available}`);
561
+ }
562
+ }
563
+ }
564
+ // Reconstruct content from modified sections
565
+ const contentParts = [];
566
+ for (const section of currentSections.values()) {
567
+ contentParts.push(section.content);
568
+ }
569
+ content = contentParts.join('\n\n');
570
+ if (context.verbose) {
571
+ console.log(`✓ Applied standalone overrides, final content has ${currentSections.size} sections`);
572
+ }
573
+ }
574
+ catch (error) {
575
+ const errorMessage = error instanceof Error ? error.message : String(error);
576
+ context.addWarning(`Standalone override processing failed: ${errorMessage}`);
577
+ }
578
+ return content;
579
+ }
580
+ /**
581
+ * Filter code blocks in content based on context file types.
582
+ *
583
+ * When context files are attached (e.g., .ts files), this method filters
584
+ * the prompt content to only keep code blocks that match those file types.
585
+ *
586
+ * Example:
587
+ * - If context contains "typescript-examples.ts", keep only ```typescript blocks
588
+ * - Non-code content and unmatched code blocks are preserved as plain text
589
+ *
590
+ * @param context - The compilation context with extracted contexts
591
+ * @param content - The markdown content to filter
592
+ * @returns Filtered content with only matching code blocks
593
+ */
594
+ filterCodeBlocksByContext(context, content) {
595
+ // Extract file extensions from context metadata
596
+ const contextExtensions = this.extractContextExtensions(context.metadata?.context);
597
+ if (contextExtensions.size === 0) {
598
+ // No recognizable file extensions in context, return content unchanged
599
+ return content;
600
+ }
601
+ // Build set of allowed language identifiers from context extensions
602
+ const allowedLanguages = new Set();
603
+ for (const ext of contextExtensions) {
604
+ const languages = (0, language_map_1.getLanguageAliasesForExtension)(ext);
605
+ languages.forEach(lang => allowedLanguages.add(lang.toLowerCase()));
606
+ }
607
+ if (allowedLanguages.size === 0) {
608
+ // No mapped languages found, return content unchanged
609
+ return content;
610
+ }
611
+ if (context.verbose) {
612
+ console.log(`Filtering code blocks for languages: ${Array.from(allowedLanguages).join(', ')}`);
613
+ }
614
+ // Parse and filter code blocks
615
+ const result = this.filterCodeBlocks(content, allowedLanguages, context.verbose);
616
+ if (context.verbose && result.removedCount > 0) {
617
+ console.log(`Removed ${result.removedCount} non-matching code block(s)`);
618
+ }
619
+ return result.content;
620
+ }
621
+ /**
622
+ * Extract file extensions from context metadata.
623
+ * Context can be a single file path string or an array of file paths.
624
+ */
625
+ extractContextExtensions(contextValue) {
626
+ const extensions = new Set();
627
+ if (!contextValue) {
628
+ return extensions;
629
+ }
630
+ // Normalize to array
631
+ const contextFiles = Array.isArray(contextValue) ? contextValue : [contextValue];
632
+ for (const filePath of contextFiles) {
633
+ if (typeof filePath === 'string') {
634
+ const ext = path.extname(filePath).toLowerCase();
635
+ if (ext) {
636
+ extensions.add(ext);
637
+ }
638
+ }
639
+ }
640
+ return extensions;
641
+ }
642
+ /**
643
+ * Filter code blocks in markdown content, keeping only those with matching languages.
644
+ *
645
+ * @param content - Markdown content with code blocks
646
+ * @param allowedLanguages - Set of allowed language identifiers (lowercase)
647
+ * @param verbose - Whether to log filtering actions
648
+ * @returns Object with filtered content and count of removed blocks
649
+ */
650
+ filterCodeBlocks(content, allowedLanguages, verbose) {
651
+ // Regex to match fenced code blocks: ```language\n...content...\n```
652
+ const codeBlockRegex = /```(\w*)\n([\s\S]*?)```/g;
653
+ let removedCount = 0;
654
+ const filteredContent = content.replace(codeBlockRegex, (match, language, blockContent) => {
655
+ const lang = (language || '').toLowerCase().trim();
656
+ // If no language specified, keep the block (could be plain text/output)
657
+ if (!lang) {
658
+ return match;
659
+ }
660
+ // Check if language is in allowed set
661
+ if (allowedLanguages.has(lang)) {
662
+ return match; // Keep matching blocks
663
+ }
664
+ // Remove non-matching code blocks
665
+ removedCount++;
666
+ if (verbose) {
667
+ const preview = blockContent.substring(0, 50).replace(/\n/g, '\\n');
668
+ console.log(` - Removing \`\`\`${lang} block: "${preview}..."`);
669
+ }
670
+ // Remove the entire code block (return empty string)
671
+ return '';
672
+ });
673
+ // Clean up multiple consecutive blank lines that may result from removal
674
+ const cleanedContent = filteredContent.replace(/\n{3,}/g, '\n\n');
675
+ return { content: cleanedContent, removedCount };
676
+ }
677
+ /**
678
+ * Process template with Jinja2/Nunjucks.
679
+ * Uses a context-aware environment with PrompdLoader to support {% include %}.
680
+ */
681
+ async processTemplate(context, content) {
682
+ if (!content) {
683
+ return content;
684
+ }
685
+ try {
686
+ // Create a context-aware Nunjucks environment with the PrompdLoader
687
+ // This enables {% include "file.prmd" %} directives
688
+ const baseDir = context.fileSystem.dirname(context.sourceFile);
689
+ const loader = new prompd_loader_1.PrompdLoader({
690
+ fileSystem: context.fileSystem,
691
+ baseDir: baseDir,
692
+ verbose: context.verbose,
693
+ maxDepth: 10
694
+ });
695
+ const env = new nunjucks.Environment(loader, {
696
+ autoescape: false,
697
+ throwOnUndefined: false,
698
+ trimBlocks: true,
699
+ lstripBlocks: true,
700
+ tags: {
701
+ blockStart: '{%',
702
+ blockEnd: '%}',
703
+ variableStart: '{{',
704
+ variableEnd: '}}',
705
+ commentStart: '{#',
706
+ commentEnd: '#}'
707
+ }
708
+ });
709
+ // Register custom filters on this environment
710
+ this.registerFiltersOnEnv(env);
711
+ // Security: Set timeout for template rendering (prevent DoS)
712
+ const renderTimeout = 5000; // 5 seconds
713
+ const renderPromise = new Promise((resolve, reject) => {
714
+ try {
715
+ const result = env.renderString(content, context.parameters);
716
+ resolve(result);
717
+ }
718
+ catch (error) {
719
+ reject(error);
720
+ }
721
+ });
722
+ const timeoutPromise = new Promise((_, reject) => {
723
+ setTimeout(() => reject(new Error('Template rendering timeout')), renderTimeout);
724
+ });
725
+ content = await Promise.race([renderPromise, timeoutPromise]);
726
+ }
727
+ catch (error) {
728
+ // Report template syntax errors properly
729
+ const errorMessage = error instanceof Error ? error.message : String(error);
730
+ // Try to extract line/column from Nunjucks error message
731
+ // Format: "(unknown path) [Line X, Column Y]" or similar
732
+ const lineMatch = errorMessage.match(/\[Line (\d+), Column (\d+)\]/i) ||
733
+ errorMessage.match(/line (\d+)/i);
734
+ const line = lineMatch ? parseInt(lineMatch[1], 10) : undefined;
735
+ const column = lineMatch && lineMatch[2] ? parseInt(lineMatch[2], 10) : undefined;
736
+ // Add as a compilation error (not just a warning)
737
+ context.addDiagnostic({
738
+ message: `Template syntax error: ${errorMessage}`,
739
+ severity: 'error',
740
+ source: 'template',
741
+ code: 'TEMPLATE_SYNTAX_ERROR',
742
+ line,
743
+ column
744
+ });
745
+ // Still fall back to simple substitution so user sees partial output
746
+ // but the error is now properly reported
747
+ content = this.enhancedSimpleSubstitution(content, context.parameters);
748
+ }
749
+ return content;
750
+ }
751
+ /**
752
+ * Register custom filters on a Nunjucks environment.
753
+ */
754
+ registerFiltersOnEnv(env) {
755
+ // Override built-in string filters to handle undefined/null safely
756
+ // This prevents "Cannot read properties of undefined" errors when template
757
+ // variables are undefined during validation (e.g., package creation)
758
+ // trim - safe version
759
+ env.addFilter('trim', (str) => {
760
+ if (str === undefined || str === null)
761
+ return '';
762
+ return String(str).trim();
763
+ });
764
+ // lower - safe version
765
+ env.addFilter('lower', (str) => {
766
+ if (str === undefined || str === null)
767
+ return '';
768
+ return String(str).toLowerCase();
769
+ });
770
+ // upper - safe version
771
+ env.addFilter('upper', (str) => {
772
+ if (str === undefined || str === null)
773
+ return '';
774
+ return String(str).toUpperCase();
775
+ });
776
+ // capitalize - safe version
777
+ env.addFilter('capitalize', (str) => {
778
+ if (str === undefined || str === null)
779
+ return '';
780
+ const s = String(str);
781
+ return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();
782
+ });
783
+ // title - safe version (capitalize each word)
784
+ env.addFilter('title', (str) => {
785
+ if (str === undefined || str === null)
786
+ return '';
787
+ return String(str).replace(/\b\w/g, c => c.toUpperCase());
788
+ });
789
+ // replace - safe version
790
+ env.addFilter('replace', (str, old, newStr, count) => {
791
+ if (str === undefined || str === null)
792
+ return '';
793
+ const s = String(str);
794
+ if (count !== undefined) {
795
+ // Replace only 'count' occurrences
796
+ let result = s;
797
+ let replaced = 0;
798
+ while (replaced < count) {
799
+ const idx = result.indexOf(old);
800
+ if (idx === -1)
801
+ break;
802
+ result = result.slice(0, idx) + newStr + result.slice(idx + old.length);
803
+ replaced++;
804
+ }
805
+ return result;
806
+ }
807
+ return s.split(old).join(newStr);
808
+ });
809
+ // striptags - safe version (remove HTML tags)
810
+ env.addFilter('striptags', (str) => {
811
+ if (str === undefined || str === null)
812
+ return '';
813
+ return String(str).replace(/<[^>]*>/g, '');
814
+ });
815
+ // urlencode - safe version
816
+ env.addFilter('urlencode', (str) => {
817
+ if (str === undefined || str === null)
818
+ return '';
819
+ return encodeURIComponent(String(str));
820
+ });
821
+ // fromcsv - Parse CSV string into array of objects
822
+ env.addFilter('fromcsv', (csvString) => {
823
+ if (!csvString || typeof csvString !== 'string') {
824
+ return [];
825
+ }
826
+ return this.parseCsv(csvString);
827
+ });
828
+ // fromjson - Parse JSON string into object/array
829
+ env.addFilter('fromjson', (jsonString) => {
830
+ if (!jsonString || typeof jsonString !== 'string') {
831
+ return null;
832
+ }
833
+ try {
834
+ return JSON.parse(jsonString);
835
+ }
836
+ catch {
837
+ return null;
838
+ }
839
+ });
840
+ // tojson - Convert object to JSON string
841
+ env.addFilter('tojson', (obj, indent) => {
842
+ try {
843
+ return JSON.stringify(obj, null, indent);
844
+ }
845
+ catch {
846
+ return '{}';
847
+ }
848
+ });
849
+ // lines - Split string into array of lines
850
+ env.addFilter('lines', (str) => {
851
+ if (!str || typeof str !== 'string') {
852
+ return [];
853
+ }
854
+ return str.split(/\r?\n/);
855
+ });
856
+ // dedent - Remove common leading whitespace from text
857
+ env.addFilter('dedent', (str) => {
858
+ if (!str || typeof str !== 'string') {
859
+ return '';
860
+ }
861
+ const lines = str.split(/\r?\n/);
862
+ let minIndent = Infinity;
863
+ for (const line of lines) {
864
+ if (line.trim().length === 0)
865
+ continue;
866
+ const match = line.match(/^(\s*)/);
867
+ if (match && match[1].length < minIndent) {
868
+ minIndent = match[1].length;
869
+ }
870
+ }
871
+ if (minIndent === Infinity)
872
+ minIndent = 0;
873
+ return lines.map(line => line.slice(minIndent)).join('\n');
874
+ });
875
+ // truncate - Truncate string with ellipsis
876
+ env.addFilter('truncate', (str, length = 80, suffix = '...') => {
877
+ if (!str || typeof str !== 'string') {
878
+ return '';
879
+ }
880
+ if (str.length <= length) {
881
+ return str;
882
+ }
883
+ return str.slice(0, length - suffix.length) + suffix;
884
+ });
885
+ // codeblock - Wrap content in fenced code block
886
+ env.addFilter('codeblock', (str, language = '') => {
887
+ if (!str || typeof str !== 'string') {
888
+ return '';
889
+ }
890
+ return '```' + language + '\n' + str + '\n```';
891
+ });
892
+ // unique - Remove duplicate items from array
893
+ env.addFilter('unique', (arr) => {
894
+ if (!Array.isArray(arr)) {
895
+ return [];
896
+ }
897
+ return [...new Set(arr)];
898
+ });
899
+ // pluck - Extract single field from array of objects
900
+ env.addFilter('pluck', (arr, field) => {
901
+ if (!Array.isArray(arr)) {
902
+ return [];
903
+ }
904
+ return arr.map(item => item?.[field]).filter(v => v !== undefined);
905
+ });
906
+ // where - Filter objects by field value
907
+ env.addFilter('where', (arr, field, value) => {
908
+ if (!Array.isArray(arr)) {
909
+ return [];
910
+ }
911
+ return arr.filter(item => item?.[field] === value);
912
+ });
913
+ // groupby - Group array items by field value
914
+ env.addFilter('groupby', (arr, field) => {
915
+ if (!Array.isArray(arr)) {
916
+ return {};
917
+ }
918
+ const result = {};
919
+ for (const item of arr) {
920
+ const key = String(item?.[field] ?? 'undefined');
921
+ if (!result[key]) {
922
+ result[key] = [];
923
+ }
924
+ result[key].push(item);
925
+ }
926
+ return result;
927
+ });
928
+ // shuffle - Randomize array order
929
+ env.addFilter('shuffle', (arr) => {
930
+ if (!Array.isArray(arr)) {
931
+ return [];
932
+ }
933
+ const shuffled = [...arr];
934
+ for (let i = shuffled.length - 1; i > 0; i--) {
935
+ const j = Math.floor(Math.random() * (i + 1));
936
+ [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
937
+ }
938
+ return shuffled;
939
+ });
940
+ // sample - Pick random N items from array
941
+ env.addFilter('sample', (arr, count = 1) => {
942
+ if (!Array.isArray(arr)) {
943
+ return [];
944
+ }
945
+ const shuffled = [...arr];
946
+ for (let i = shuffled.length - 1; i > 0; i--) {
947
+ const j = Math.floor(Math.random() * (i + 1));
948
+ [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
949
+ }
950
+ return shuffled.slice(0, Math.min(count, shuffled.length));
951
+ });
952
+ // wordwrap - Wrap text at specified width
953
+ env.addFilter('wordwrap', (str, width = 80) => {
954
+ if (!str || typeof str !== 'string') {
955
+ return '';
956
+ }
957
+ const words = str.split(/\s+/);
958
+ const lines = [];
959
+ let currentLine = '';
960
+ for (const word of words) {
961
+ if (currentLine.length + word.length + 1 <= width) {
962
+ currentLine += (currentLine ? ' ' : '') + word;
963
+ }
964
+ else {
965
+ if (currentLine)
966
+ lines.push(currentLine);
967
+ currentLine = word;
968
+ }
969
+ }
970
+ if (currentLine)
971
+ lines.push(currentLine);
972
+ return lines.join('\n');
973
+ });
974
+ // bulletlist - Convert array/lines to bullet list
975
+ env.addFilter('bulletlist', (input) => {
976
+ const items = Array.isArray(input) ? input : String(input).split(/\r?\n/);
977
+ return items.filter(item => String(item).trim()).map(item => `- ${item}`).join('\n');
978
+ });
979
+ // numberedlist - Convert array/lines to numbered list
980
+ env.addFilter('numberedlist', (input) => {
981
+ const items = Array.isArray(input) ? input : String(input).split(/\r?\n/);
982
+ return items.filter(item => String(item).trim()).map((item, i) => `${i + 1}. ${item}`).join('\n');
983
+ });
984
+ }
985
+ /**
986
+ * Enhanced simple substitution with nested property access.
987
+ * Supports both single brace {var} and double brace {{var}} syntax for backward compatibility.
988
+ */
989
+ enhancedSimpleSubstitution(content, parameters) {
990
+ // Guard against undefined/null content
991
+ if (content === undefined || content === null) {
992
+ return '';
993
+ }
994
+ // Helper function to resolve nested property path
995
+ const resolveValue = (fullPath) => {
996
+ const parts = fullPath.split('.');
997
+ let value = parameters[parts[0]];
998
+ if (value === undefined || value === null) {
999
+ return null;
1000
+ }
1001
+ try {
1002
+ for (let i = 1; i < parts.length; i++) {
1003
+ if (typeof value === 'object' && value !== null) {
1004
+ value = value[parts[i]];
1005
+ }
1006
+ else {
1007
+ return null;
1008
+ }
1009
+ if (value === undefined || value === null) {
1010
+ return null;
1011
+ }
1012
+ }
1013
+ return String(value);
1014
+ }
1015
+ catch {
1016
+ return null;
1017
+ }
1018
+ };
1019
+ // First pass: Handle double braces {{ obj.prop }} (Jinja2/Nunjucks standard)
1020
+ // Supports optional whitespace and whitespace trimming markers (-~)
1021
+ content = content.replace(/\{\{[-~]?\s*([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*)\s*[-~]?\}\}/g, (_match, fullPath) => {
1022
+ const resolved = resolveValue(fullPath);
1023
+ return resolved !== null ? resolved : `[Missing: ${fullPath}]`;
1024
+ });
1025
+ // Second pass: Handle single braces {obj.prop} (legacy syntax)
1026
+ // Only match single braces that aren't part of {{ }} or {% %}
1027
+ content = content.replace(/(?<!\{)\{([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*)\}(?!\})/g, (_match, fullPath) => {
1028
+ const resolved = resolveValue(fullPath);
1029
+ return resolved !== null ? resolved : `[Missing: ${fullPath}]`;
1030
+ });
1031
+ return content;
1032
+ }
1033
+ /**
1034
+ * Escape special regex characters.
1035
+ */
1036
+ escapeRegex(str) {
1037
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1038
+ }
1039
+ getName() {
1040
+ return 'Template Processing';
1041
+ }
1042
+ }
1043
+ exports.TemplateProcessingStage = TemplateProcessingStage;
1044
+ //# sourceMappingURL=template.js.map