@visulima/vis 1.0.0-alpha.11 → 1.0.0-alpha.13

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 (116) hide show
  1. package/CHANGELOG.md +101 -0
  2. package/LICENSE.md +559 -186
  3. package/README.md +18 -0
  4. package/dist/bin.js +1 -9
  5. package/dist/config/index.d.ts +477 -556
  6. package/dist/config/index.js +1 -2
  7. package/dist/generate/index.js +1 -3
  8. package/dist/packem_chunks/applyDefaults.js +2 -336
  9. package/dist/packem_chunks/bin.js +234 -9552
  10. package/dist/packem_chunks/doctor-probe.js +2 -112
  11. package/dist/packem_chunks/fix.js +11 -234
  12. package/dist/packem_chunks/handler.js +1 -99
  13. package/dist/packem_chunks/handler10.js +2 -53
  14. package/dist/packem_chunks/handler11.js +1 -32
  15. package/dist/packem_chunks/handler12.js +5 -100
  16. package/dist/packem_chunks/handler13.js +1 -25
  17. package/dist/packem_chunks/handler14.js +18 -916
  18. package/dist/packem_chunks/handler15.js +15 -201
  19. package/dist/packem_chunks/handler16.js +1 -124
  20. package/dist/packem_chunks/handler17.js +1 -13
  21. package/dist/packem_chunks/handler18.js +1 -106
  22. package/dist/packem_chunks/handler19.js +1 -19
  23. package/dist/packem_chunks/handler2.js +2 -75
  24. package/dist/packem_chunks/handler20.js +5 -29
  25. package/dist/packem_chunks/handler21.js +1 -222
  26. package/dist/packem_chunks/handler22.js +1 -237
  27. package/dist/packem_chunks/handler23.js +5 -101
  28. package/dist/packem_chunks/handler24.js +1 -110
  29. package/dist/packem_chunks/handler25.js +3 -402
  30. package/dist/packem_chunks/handler26.js +1 -13
  31. package/dist/packem_chunks/handler27.js +1 -63
  32. package/dist/packem_chunks/handler28.js +7 -34
  33. package/dist/packem_chunks/handler29.js +21 -456
  34. package/dist/packem_chunks/handler3.js +4 -95
  35. package/dist/packem_chunks/handler30.js +3 -170
  36. package/dist/packem_chunks/handler31.js +1 -530
  37. package/dist/packem_chunks/handler32.js +2 -214
  38. package/dist/packem_chunks/handler33.js +25 -119
  39. package/dist/packem_chunks/handler34.js +2 -630
  40. package/dist/packem_chunks/handler35.js +3 -283
  41. package/dist/packem_chunks/handler36.js +22 -542
  42. package/dist/packem_chunks/handler37.js +410 -744
  43. package/dist/packem_chunks/handler38.js +22 -989
  44. package/dist/packem_chunks/handler39.js +22 -574
  45. package/dist/packem_chunks/handler4.js +2 -90
  46. package/dist/packem_chunks/handler40.js +22 -1685
  47. package/dist/packem_chunks/handler41.js +6 -1088
  48. package/dist/packem_chunks/handler42.js +5 -797
  49. package/dist/packem_chunks/handler43.js +10 -2658
  50. package/dist/packem_chunks/handler44.js +51 -3784
  51. package/dist/packem_chunks/handler45.js +25 -2574
  52. package/dist/packem_chunks/handler46.js +3 -3769
  53. package/dist/packem_chunks/handler47.js +21 -1485
  54. package/dist/packem_chunks/handler48.js +42 -0
  55. package/dist/packem_chunks/handler5.js +8 -174
  56. package/dist/packem_chunks/handler6.js +1 -95
  57. package/dist/packem_chunks/handler7.js +1 -115
  58. package/dist/packem_chunks/handler8.js +1 -12
  59. package/dist/packem_chunks/handler9.js +1 -29
  60. package/dist/packem_chunks/heal-accept.js +10 -522
  61. package/dist/packem_chunks/heal.js +14 -673
  62. package/dist/packem_chunks/index.js +7 -873
  63. package/dist/packem_chunks/loader.js +1 -23
  64. package/dist/packem_chunks/tar.js +3 -0
  65. package/dist/packem_shared/ai-analysis-hm8d2W7z.js +67 -0
  66. package/dist/packem_shared/ai-cache-DoiF80AR.js +1 -0
  67. package/dist/packem_shared/ai-fix-nn4zOE95.js +43 -0
  68. package/dist/packem_shared/cache-directory-CwHlJhgx.js +1 -0
  69. package/dist/packem_shared/dependency-scan-COr5n63B.js +2 -0
  70. package/dist/packem_shared/docker-D6OGr5_S.js +2 -0
  71. package/dist/packem_shared/failure-log-iUVLf6ts.js +2 -0
  72. package/dist/packem_shared/flakiness-D9wf0t56.js +1 -0
  73. package/dist/packem_shared/giget-CcEy_Elm.js +2 -0
  74. package/dist/packem_shared/index-DH-5hsrC.js +1 -0
  75. package/dist/packem_shared/otel-DxDUPJJH.js +6 -0
  76. package/dist/packem_shared/otelPlugin-CQq6poq8.js +1 -0
  77. package/dist/packem_shared/registry-CkubDdiY.js +2 -0
  78. package/dist/packem_shared/run-summary-utils-BfBvjzhY.js +1 -0
  79. package/dist/packem_shared/runtime-check-BXZ43CBW.js +1 -0
  80. package/dist/packem_shared/selectors-BylODRiM.js +3 -0
  81. package/dist/packem_shared/symbols-CQmER5MT.js +1 -0
  82. package/dist/packem_shared/toolchain-BgBOUHII.js +5 -0
  83. package/dist/packem_shared/typosquats-CcZl99B1.js +1 -0
  84. package/dist/packem_shared/use-measured-height-DjYgUOKk.js +1 -0
  85. package/dist/packem_shared/utils-DrNg0XTR.js +1 -0
  86. package/dist/packem_shared/verify-Baj5mFJ7.js +1 -0
  87. package/dist/packem_shared/vis-update-app-D1jl0UZZ.js +1 -0
  88. package/dist/packem_shared/xxh3-DrAUNq4n.js +1 -0
  89. package/index.js +556 -727
  90. package/package.json +19 -29
  91. package/schemas/project.schema.json +739 -297
  92. package/schemas/vis-config.schema.json +3365 -384
  93. package/templates/buildkite-ci/template.yml +20 -20
  94. package/dist/packem_shared/VisUpdateApp-D-Yz_wvg.js +0 -1316
  95. package/dist/packem_shared/_commonjsHelpers-BqLXS_qQ.js +0 -5
  96. package/dist/packem_shared/ai-analysis-CHeB1joD.js +0 -367
  97. package/dist/packem_shared/ai-cache-Be_jexe4.js +0 -142
  98. package/dist/packem_shared/ai-fix-B9iQVcD2.js +0 -379
  99. package/dist/packem_shared/cache-directory-2qvs4goY.js +0 -98
  100. package/dist/packem_shared/catalog-BJTtyi-O.js +0 -1371
  101. package/dist/packem_shared/dependency-scan-A0KSklpG.js +0 -188
  102. package/dist/packem_shared/docker-2iZzc280.js +0 -181
  103. package/dist/packem_shared/failure-log-Cz3Z4SKL.js +0 -100
  104. package/dist/packem_shared/flakiness-goTxXuCX.js +0 -180
  105. package/dist/packem_shared/otel-DCvqCTz_.js +0 -158
  106. package/dist/packem_shared/otelPlugin-DFaLDvJf.js +0 -3
  107. package/dist/packem_shared/registry-CbqXI0rc.js +0 -272
  108. package/dist/packem_shared/run-summary-utils-PVMl4aIh.js +0 -130
  109. package/dist/packem_shared/runtime-check-Cobi3p6l.js +0 -127
  110. package/dist/packem_shared/selectors-SM69TfqC.js +0 -194
  111. package/dist/packem_shared/symbols-Ta7g2nU-.js +0 -14
  112. package/dist/packem_shared/toolchain-BdZd9eBi.js +0 -975
  113. package/dist/packem_shared/typosquats-C-bCh3PX.js +0 -1210
  114. package/dist/packem_shared/use-measured-height-CNP0vT4M.js +0 -20
  115. package/dist/packem_shared/utils-CthVdBPS.js +0 -40
  116. package/dist/packem_shared/xxh3-Ck8mXNg1.js +0 -239
@@ -1,873 +1,7 @@
1
- import { walkSync, readFileSync } from '@visulima/fs';
2
- import { readYamlSync } from '@visulima/fs/yaml';
3
- import { relative, join } from '@visulima/path';
4
- import { parse as parse$1 } from 'yaml';
5
- import { constantCase, trainCase, snakeCase, pascalCase, kebabCase, camelCase } from '@visulima/string/case';
6
-
7
- const stringify$2 = (value) => {
8
- if (value == null) {
9
- return "";
10
- }
11
- return typeof value === "string" ? value : String(value);
12
- };
13
- const FILTERS = {
14
- // ── Eight case filters (moon parity) ─────────────────────────────
15
- camel_case: (value) => camelCase(stringify$2(value)),
16
- kebab_case: (value) => kebabCase(stringify$2(value)),
17
- lower_case: (value) => stringify$2(value).toLowerCase(),
18
- pascal_case: (value) => pascalCase(stringify$2(value)),
19
- // ── Path filters (moon parity) ───────────────────────────────────
20
- /**
21
- * `path_join`: join the input string with the filter argument as
22
- * an additional path segment. Matches moon's `value | path_join("subdir")`.
23
- */
24
- path_join: (value, ...args) => {
25
- const segments = [stringify$2(value), ...args.map(stringify$2)];
26
- return join(...segments);
27
- },
28
- /**
29
- * `path_relative`: compute a relative path from the filter argument
30
- * (the "from" base) to the value (the "to" target). Matches moon's
31
- * `to | path_relative(from)`.
32
- */
33
- path_relative: (value, base) => relative(stringify$2(base), stringify$2(value)),
34
- snake_case: (value) => snakeCase(stringify$2(value)),
35
- upper_case: (value) => stringify$2(value).toUpperCase(),
36
- upper_kebab_case: (value) => trainCase(stringify$2(value)),
37
- upper_snake_case: (value) => constantCase(stringify$2(value))
38
- };
39
- const applyFilter = (name, value, args = []) => {
40
- const fn = FILTERS[name];
41
- if (!fn) {
42
- throw new Error(`Unknown filter "${name}". Known filters: ${Object.keys(FILTERS).sort().join(", ")}.`);
43
- }
44
- return fn(value, ...args);
45
- };
46
-
47
- const splitCommaOutsideQuotes = (input) => {
48
- const parts = [];
49
- let depth = 0;
50
- let start = 0;
51
- let inQuote;
52
- let index = -1;
53
- for (const character of input) {
54
- index += 1;
55
- if (inQuote) {
56
- if (character === inQuote) {
57
- inQuote = void 0;
58
- }
59
- continue;
60
- }
61
- if (character === '"' || character === "'") {
62
- inQuote = character;
63
- continue;
64
- }
65
- if (character === "(") {
66
- depth += 1;
67
- } else if (character === ")") {
68
- depth -= 1;
69
- } else if (character === "," && depth === 0) {
70
- parts.push(input.slice(start, index));
71
- start = index + 1;
72
- }
73
- }
74
- parts.push(input.slice(start));
75
- return parts;
76
- };
77
- const stripQuotes$1 = (input) => {
78
- if (input.startsWith('"') && input.endsWith('"') || input.startsWith("'") && input.endsWith("'")) {
79
- return input.slice(1, -1);
80
- }
81
- return void 0;
82
- };
83
-
84
- const BRACKET_PATTERN = /\[([^\]]+)\]/g;
85
- const parseFilterCall$1 = (chunk) => {
86
- const trimmed = chunk.trim();
87
- const openParen = trimmed.indexOf("(");
88
- if (openParen === -1) {
89
- return { args: [], name: trimmed };
90
- }
91
- if (!trimmed.endsWith(")")) {
92
- throw new Error(`Filter call "${chunk}" missing closing ")"`);
93
- }
94
- const name = trimmed.slice(0, openParen).trim();
95
- const argsRaw = trimmed.slice(openParen + 1, -1).trim();
96
- if (argsRaw === "") {
97
- return { args: [], name };
98
- }
99
- const args = splitCommaOutsideQuotes(argsRaw).map((raw) => {
100
- const trimmedArg = raw.trim();
101
- const unquoted = stripQuotes$1(trimmedArg);
102
- return unquoted ?? trimmedArg;
103
- });
104
- return { args, name };
105
- };
106
- const parsePipe = (expr) => {
107
- const parts = expr.split("|").map((s) => s.trim());
108
- const name = parts[0] ?? "";
109
- if (!name) {
110
- throw new Error(`Empty variable name in expression "${expr}"`);
111
- }
112
- const filters = parts.slice(1).map(parseFilterCall$1);
113
- return { filters, name };
114
- };
115
- const stringify$1 = (value) => {
116
- if (value == null) {
117
- return "";
118
- }
119
- return typeof value === "string" ? value : String(value);
120
- };
121
- const interpolateFilename = (filename, vars) => {
122
- const interpolated = filename.replaceAll(BRACKET_PATTERN, (_, expr) => {
123
- const { filters, name } = parsePipe(expr);
124
- if (!Object.hasOwn(vars, name)) {
125
- throw new Error(`Variable "${name}" used in filename "${filename}" but not defined`);
126
- }
127
- let value = vars[name];
128
- for (const filter of filters) {
129
- value = applyFilter(filter.name, value, filter.args);
130
- }
131
- return stringify$1(value);
132
- });
133
- return stripTeraSuffix(interpolated);
134
- };
135
- const stripTeraSuffix = (filename) => filename.split("/").map((segment) => {
136
- if (segment.endsWith(".tera")) {
137
- return segment.slice(0, -5);
138
- }
139
- if (segment.endsWith(".twig")) {
140
- return segment.slice(0, -5);
141
- }
142
- return segment;
143
- }).join("/");
144
- const stripRawSuffix = (filename) => filename.endsWith(".raw") ? filename.slice(0, -4) : filename;
145
- const isPartialPath = (path) => {
146
- const segments = path.split("/");
147
- const name = segments.at(-1) ?? "";
148
- if (name.startsWith("_")) {
149
- return true;
150
- }
151
- return segments.slice(0, -1).includes("partials");
152
- };
153
-
154
- const FENCE = "---";
155
- const splitFrontmatter = (source, parseYaml) => {
156
- if (!source.startsWith(`${FENCE}
157
- `) && !source.startsWith(`${FENCE}\r
158
- `)) {
159
- return { body: source };
160
- }
161
- const headerEnd = source.indexOf(`
162
- ${FENCE}`, FENCE.length);
163
- if (headerEnd === -1) {
164
- return { body: source };
165
- }
166
- let bodyStart = headerEnd + 1 + FENCE.length;
167
- if (source[bodyStart] === "\r") {
168
- bodyStart += 1;
169
- }
170
- if (source[bodyStart] === "\n") {
171
- bodyStart += 1;
172
- }
173
- const headerStart = source.startsWith(`${FENCE}\r
174
- `) ? FENCE.length + 2 : FENCE.length + 1;
175
- const yaml = source.slice(headerStart, headerEnd).replaceAll("\r", "");
176
- let parsed;
177
- try {
178
- parsed = parseYaml(yaml);
179
- } catch (error) {
180
- const message = error instanceof Error ? error.message : String(error);
181
- throw new Error(`Failed to parse frontmatter YAML: ${message}`);
182
- }
183
- if (parsed === null || parsed === void 0) {
184
- return { body: source.slice(bodyStart) };
185
- }
186
- if (typeof parsed !== "object" || Array.isArray(parsed)) {
187
- throw new TypeError(`Frontmatter must be a YAML mapping, got ${Array.isArray(parsed) ? "array" : typeof parsed}`);
188
- }
189
- return { body: source.slice(bodyStart), frontmatter: parsed };
190
- };
191
-
192
- const TAG_PATTERN = /\{\{-?(.+?)-?\}\}|\{%-?(.+?)-?%\}/gs;
193
- const tokenize = (source) => {
194
- const tokens = [];
195
- let lastIndex = 0;
196
- let line = 1;
197
- const advanceLine = (text) => {
198
- for (const character of text) {
199
- if (character === "\n") {
200
- line += 1;
201
- }
202
- }
203
- };
204
- for (const match of source.matchAll(TAG_PATTERN)) {
205
- const start = match.index ?? 0;
206
- if (start > lastIndex) {
207
- const text = source.slice(lastIndex, start);
208
- tokens.push({ line, type: "text", value: text });
209
- advanceLine(text);
210
- }
211
- const startLine = line;
212
- const full = match[0];
213
- if (full.startsWith("{{")) {
214
- tokens.push({ line: startLine, type: "expr", value: (match[1] ?? "").trim() });
215
- } else {
216
- tokens.push({ line: startLine, type: "stmt", value: (match[2] ?? "").trim() });
217
- }
218
- advanceLine(full);
219
- lastIndex = start + full.length;
220
- }
221
- if (lastIndex < source.length) {
222
- const text = source.slice(lastIndex);
223
- tokens.push({ line, type: "text", value: text });
224
- }
225
- return tokens;
226
- };
227
- const UNSUPPORTED_KEYWORDS = /* @__PURE__ */ new Set(["block", "endblock", "endfilter", "endmacro", "extends", "filter", "import", "macro", "set"]);
228
- const parse = (tokens, filename) => {
229
- let cursor = 0;
230
- const error = (line, message) => {
231
- throw new Error(`${filename}:${line}: ${message}`);
232
- };
233
- const parseBlock = (terminators) => {
234
- const nodes = [];
235
- while (cursor < tokens.length) {
236
- const token = tokens[cursor];
237
- if (token.type === "text") {
238
- nodes.push({ type: "text", value: token.value });
239
- cursor += 1;
240
- continue;
241
- }
242
- if (token.type === "expr") {
243
- nodes.push({ expression: token.value, line: token.line, type: "expr" });
244
- cursor += 1;
245
- continue;
246
- }
247
- const head = token.value.split(/\s+/)[0] ?? "";
248
- if (terminators.includes(head)) {
249
- return nodes;
250
- }
251
- if (UNSUPPORTED_KEYWORDS.has(head)) {
252
- error(
253
- token.line,
254
- `Tera feature "{% ${head} %}" is not supported. Supported: if/else/endif, for/endfor, include. Rewrite the template to avoid macros, set, extends, block, and import.`
255
- );
256
- }
257
- if (head === "if") {
258
- cursor += 1;
259
- const condition = token.value.slice(2).trim();
260
- const consequent = parseBlock(["else", "endif"]);
261
- let alternate;
262
- if (cursor < tokens.length && tokens[cursor].type === "stmt" && tokens[cursor].value.split(/\s+/)[0] === "else") {
263
- cursor += 1;
264
- alternate = parseBlock(["endif"]);
265
- }
266
- if (cursor >= tokens.length || tokens[cursor].type !== "stmt" || tokens[cursor].value.split(/\s+/)[0] !== "endif") {
267
- error(token.line, "Unterminated {% if %} — missing {% endif %}");
268
- }
269
- cursor += 1;
270
- nodes.push({ alternate, condition, consequent, line: token.line, type: "if" });
271
- continue;
272
- }
273
- if (head === "for") {
274
- cursor += 1;
275
- const rest = token.value.slice(3).trim();
276
- const inIndex = rest.indexOf(" in ");
277
- if (inIndex === -1) {
278
- error(token.line, "Malformed {% for %} — expected `for <name> in <collection>`");
279
- }
280
- const binding = rest.slice(0, inIndex).trim();
281
- const collection = rest.slice(inIndex + 4).trim();
282
- if (!binding || !collection) {
283
- error(token.line, "Malformed {% for %} — missing binding or collection");
284
- }
285
- const body = parseBlock(["endfor"]);
286
- if (cursor >= tokens.length || tokens[cursor].type !== "stmt" || tokens[cursor].value.split(/\s+/)[0] !== "endfor") {
287
- error(token.line, "Unterminated {% for %} — missing {% endfor %}");
288
- }
289
- cursor += 1;
290
- nodes.push({ binding, body, collection, line: token.line, type: "for" });
291
- continue;
292
- }
293
- if (head === "include") {
294
- cursor += 1;
295
- const argument = token.value.slice(7).trim();
296
- const stripped = stripQuotes(argument);
297
- if (stripped === void 0) {
298
- error(token.line, "Malformed {% include %} — expected a quoted partial name");
299
- }
300
- nodes.push({ line: token.line, name: stripped, type: "include" });
301
- continue;
302
- }
303
- error(token.line, `Unknown tag "{% ${head} %}". Supported: if, for, include.`);
304
- }
305
- return nodes;
306
- };
307
- return parseBlock([]);
308
- };
309
- const stripQuotes = (input) => {
310
- if (input.startsWith('"') && input.endsWith('"') || input.startsWith("'") && input.endsWith("'")) {
311
- return input.slice(1, -1);
312
- }
313
- return void 0;
314
- };
315
- const stringify = (value) => {
316
- if (value == null) {
317
- return "";
318
- }
319
- if (typeof value === "boolean" || typeof value === "number") {
320
- return String(value);
321
- }
322
- if (typeof value === "string") {
323
- return value;
324
- }
325
- return JSON.stringify(value);
326
- };
327
- const isTruthy = (value) => {
328
- if (value === null || value === void 0 || value === false || value === 0 || value === "") {
329
- return false;
330
- }
331
- if (Array.isArray(value)) {
332
- return value.length > 0;
333
- }
334
- return true;
335
- };
336
- const lookupVariable = (path, scope, line, filename, strict = true) => {
337
- const segments = path.split(".");
338
- if (strict && segments.length > 0 && !Object.hasOwn(scope, segments[0])) {
339
- throw new Error(`${filename}:${line}: Variable "${path}" is not defined`);
340
- }
341
- let value = scope;
342
- for (const segment of segments) {
343
- if (value === null || value === void 0) {
344
- return void 0;
345
- }
346
- if (typeof value !== "object") {
347
- throw new TypeError(`${filename}:${line}: Cannot read "${segment}" on non-object value`);
348
- }
349
- value = value[segment];
350
- }
351
- return value;
352
- };
353
- const splitFilterPipe = (expression) => {
354
- const parts = [];
355
- let depth = 0;
356
- let start = 0;
357
- let inQuote;
358
- for (let index = 0; index < expression.length; index += 1) {
359
- const character = expression[index];
360
- if (inQuote) {
361
- if (character === inQuote) {
362
- inQuote = void 0;
363
- }
364
- continue;
365
- }
366
- if (character === '"' || character === "'") {
367
- inQuote = character;
368
- continue;
369
- }
370
- if (character === "(") {
371
- depth += 1;
372
- } else if (character === ")") {
373
- depth -= 1;
374
- } else if (character === "|" && depth === 0) {
375
- parts.push(expression.slice(start, index));
376
- start = index + 1;
377
- }
378
- }
379
- parts.push(expression.slice(start));
380
- return parts;
381
- };
382
- const parseFilterCall = (chunk) => {
383
- const trimmed = chunk.trim();
384
- const openParen = trimmed.indexOf("(");
385
- if (openParen === -1) {
386
- return { args: [], name: trimmed };
387
- }
388
- if (!trimmed.endsWith(")")) {
389
- throw new Error(`Filter call "${chunk}" missing closing ")"`);
390
- }
391
- const name = trimmed.slice(0, openParen).trim();
392
- const argsRaw = trimmed.slice(openParen + 1, -1).trim();
393
- if (!argsRaw) {
394
- return { args: [], name };
395
- }
396
- return { args: splitCommaOutsideQuotes(argsRaw).map((s) => s.trim()), name };
397
- };
398
- const evaluatePrimary = (token, scope, line, filename, strict = true) => {
399
- const trimmed = token.trim();
400
- if (trimmed === "true") {
401
- return true;
402
- }
403
- if (trimmed === "false") {
404
- return false;
405
- }
406
- if (trimmed === "null" || trimmed === "none") {
407
- return null;
408
- }
409
- const stripped = stripQuotes(trimmed);
410
- if (stripped !== void 0) {
411
- return stripped;
412
- }
413
- if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
414
- return Number(trimmed);
415
- }
416
- return lookupVariable(trimmed, scope, line, filename, strict);
417
- };
418
- const evaluateExpression = (expression, scope, line, filename, strict = true) => {
419
- const segments = splitFilterPipe(expression);
420
- const head = segments[0].trim();
421
- let value = evaluatePrimary(head, scope, line, filename, strict);
422
- for (let index = 1; index < segments.length; index += 1) {
423
- const filterCall = segments[index].trim();
424
- const { args, name } = parseFilterCall(filterCall);
425
- const resolvedArgs = args.map((argument) => evaluatePrimary(argument, scope, line, filename, strict));
426
- try {
427
- value = applyFilter(name, value, resolvedArgs);
428
- } catch (error_) {
429
- const message = error_ instanceof Error ? error_.message : String(error_);
430
- throw new Error(`${filename}:${line}: ${message}`);
431
- }
432
- }
433
- return value;
434
- };
435
- const findOperatorOutsideQuotes = (input, operator) => {
436
- let inQuote;
437
- let depth = 0;
438
- for (let index = 0; index <= input.length - operator.length; index += 1) {
439
- const character = input[index];
440
- if (inQuote) {
441
- if (character === inQuote) {
442
- inQuote = void 0;
443
- }
444
- continue;
445
- }
446
- if (character === '"' || character === "'") {
447
- inQuote = character;
448
- continue;
449
- }
450
- if (character === "(") {
451
- depth += 1;
452
- continue;
453
- }
454
- if (character === ")") {
455
- depth -= 1;
456
- continue;
457
- }
458
- if (depth !== 0) {
459
- continue;
460
- }
461
- if (input.slice(index, index + operator.length) === operator) {
462
- return index;
463
- }
464
- }
465
- return -1;
466
- };
467
- const splitLogical = (input, keyword) => {
468
- const parts = [];
469
- let start = 0;
470
- let index = 0;
471
- while (index <= input.length - keyword.length) {
472
- const at = findOperatorOutsideQuotes(input.slice(index), keyword);
473
- if (at === -1) {
474
- break;
475
- }
476
- parts.push(input.slice(start, index + at));
477
- index += at + keyword.length;
478
- start = index;
479
- }
480
- parts.push(input.slice(start));
481
- return parts;
482
- };
483
- const evaluateConditionExpression = (condition, scope, filename, line) => {
484
- const trimmed = condition.trim();
485
- if (trimmed === "") {
486
- return false;
487
- }
488
- if (trimmed.startsWith("(") && trimmed.endsWith(")") && matchingParen(trimmed) === trimmed.length - 1) {
489
- return evaluateConditionExpression(trimmed.slice(1, -1), scope, filename, line);
490
- }
491
- const orParts = splitLogical(trimmed, " or ");
492
- if (orParts.length > 1) {
493
- return orParts.some((part) => evaluateConditionExpression(part, scope, filename, line));
494
- }
495
- const andParts = splitLogical(trimmed, " and ");
496
- if (andParts.length > 1) {
497
- return andParts.every((part) => evaluateConditionExpression(part, scope, filename, line));
498
- }
499
- if (trimmed.startsWith("not ")) {
500
- return !evaluateConditionExpression(trimmed.slice(4), scope, filename, line);
501
- }
502
- for (const operator of ["==", "!="]) {
503
- const index = findOperatorOutsideQuotes(trimmed, operator);
504
- if (index !== -1) {
505
- const left = evaluatePrimary(trimmed.slice(0, index), scope, line, filename, false);
506
- const right = evaluatePrimary(trimmed.slice(index + operator.length), scope, line, filename, false);
507
- const equal = left === right;
508
- return operator === "==" ? equal : !equal;
509
- }
510
- }
511
- return isTruthy(evaluateExpression(trimmed, scope, line, filename, false));
512
- };
513
- const matchingParen = (input) => {
514
- if (input[0] !== "(") {
515
- return -1;
516
- }
517
- let depth = 0;
518
- let inQuote;
519
- let index = -1;
520
- for (const character of input) {
521
- index += 1;
522
- if (inQuote) {
523
- if (character === inQuote) {
524
- inQuote = void 0;
525
- }
526
- continue;
527
- }
528
- if (character === '"' || character === "'") {
529
- inQuote = character;
530
- continue;
531
- }
532
- if (character === "(") {
533
- depth += 1;
534
- } else if (character === ")") {
535
- depth -= 1;
536
- if (depth === 0) {
537
- return index;
538
- }
539
- }
540
- }
541
- return -1;
542
- };
543
- const renderNodes = (nodes, options) => {
544
- let output = "";
545
- for (const node of nodes) {
546
- if (node.type === "text") {
547
- output += node.value;
548
- continue;
549
- }
550
- if (node.type === "expr") {
551
- output += stringify(evaluateExpression(node.expression, options.scope, node.line, options.filename));
552
- continue;
553
- }
554
- if (node.type === "if") {
555
- const branch = evaluateConditionExpression(node.condition, options.scope, options.filename, node.line) ? node.consequent : node.alternate ?? [];
556
- output += renderNodes(branch, options);
557
- continue;
558
- }
559
- if (node.type === "for") {
560
- const collection = lookupVariable(node.collection, options.scope, node.line, options.filename, false);
561
- if (collection === void 0 || collection === null) {
562
- continue;
563
- }
564
- if (!Array.isArray(collection)) {
565
- throw new TypeError(`${options.filename}:${node.line}: {% for %} expected an array, got ${typeof collection}`);
566
- }
567
- for (const item of collection) {
568
- const childScope = { ...options.scope, [node.binding]: item };
569
- output += renderNodes(node.body, { ...options, scope: childScope });
570
- }
571
- continue;
572
- }
573
- if (node.type === "include") {
574
- const ast = options.partials?.get(node.name);
575
- if (!ast) {
576
- throw new Error(
577
- `${options.filename}:${node.line}: Partial "${node.name}" not found. Available: ${[...options.partials?.keys() ?? []].join(", ") || "(none)"}`
578
- );
579
- }
580
- const stack = options.includeStack ?? /* @__PURE__ */ new Set();
581
- if (stack.has(node.name)) {
582
- const chain = [...stack, node.name].join(" → ");
583
- throw new Error(`${options.filename}:${node.line}: Circular partial include detected: ${chain}`);
584
- }
585
- stack.add(node.name);
586
- try {
587
- output += renderNodes(ast, { ...options, filename: `<partial:${node.name}>`, includeStack: stack });
588
- } finally {
589
- stack.delete(node.name);
590
- }
591
- }
592
- }
593
- return output;
594
- };
595
- const parseTemplate = (source, filename) => parse(tokenize(source), filename);
596
- const renderTemplate = (source, options) => renderNodes(parse(tokenize(source), options.filename), options);
597
-
598
- const TEMPLATE_FILENAME = "template.yml";
599
- const RAW_SUFFIX = ".raw";
600
- const TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
601
- ".cjs",
602
- ".css",
603
- ".env",
604
- ".gitattributes",
605
- ".gitignore",
606
- ".graphql",
607
- ".html",
608
- ".ini",
609
- ".js",
610
- ".json",
611
- ".json5",
612
- ".jsonc",
613
- ".jsx",
614
- ".lock",
615
- ".md",
616
- ".mdx",
617
- ".mjs",
618
- ".prettierrc",
619
- ".raw",
620
- ".rs",
621
- ".scss",
622
- ".sh",
623
- ".sql",
624
- ".svg",
625
- ".tera",
626
- ".toml",
627
- ".ts",
628
- ".tsx",
629
- ".twig",
630
- ".txt",
631
- ".vue",
632
- ".xml",
633
- ".yaml",
634
- ".yml"
635
- ]);
636
- const BARE_TEXT_BASENAMES = /* @__PURE__ */ new Set([
637
- "Brewfile",
638
- "CHANGELOG",
639
- "CODEOWNERS",
640
- "CONTRIBUTING",
641
- "COPYING",
642
- "Dockerfile",
643
- "Gemfile",
644
- "LICENCE",
645
- "LICENSE",
646
- "Makefile",
647
- "NOTICE",
648
- "Procfile",
649
- "Rakefile",
650
- "README",
651
- "VERSION"
652
- ]);
653
- const lastDot = (path) => {
654
- const slash = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
655
- const segment = slash === -1 ? path : path.slice(slash + 1);
656
- const dot = segment.lastIndexOf(".");
657
- if (dot === -1 || dot === 0) {
658
- return "";
659
- }
660
- return segment.slice(dot).toLowerCase();
661
- };
662
- const basename = (path) => {
663
- const slash = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
664
- return slash === -1 ? path : path.slice(slash + 1);
665
- };
666
- const isLikelyText = (path) => {
667
- const ext = lastDot(path);
668
- if (ext !== "") {
669
- return TEXT_EXTENSIONS.has(ext);
670
- }
671
- const name = basename(path);
672
- if (BARE_TEXT_BASENAMES.has(name)) {
673
- return true;
674
- }
675
- return false;
676
- };
677
- const yamlVariableToVariable = (variable) => {
678
- const baseFields = {
679
- default: variable.default,
680
- internal: variable.internal,
681
- order: variable.order,
682
- prompt: variable.prompt,
683
- required: variable.required
684
- };
685
- switch (variable.type) {
686
- case "array": {
687
- return { ...baseFields, type: "array" };
688
- }
689
- case "boolean": {
690
- return { ...baseFields, default: typeof baseFields.default === "boolean" ? baseFields.default : void 0, type: "boolean" };
691
- }
692
- case "enum": {
693
- return {
694
- ...baseFields,
695
- multiple: variable.multiple,
696
- type: "enum",
697
- values: variable.values ?? []
698
- };
699
- }
700
- case "number": {
701
- return { ...baseFields, type: "number" };
702
- }
703
- case "string": {
704
- return { ...baseFields, type: "string" };
705
- }
706
- default: {
707
- throw new Error(`Unsupported variable type "${String(variable.type)}"`);
708
- }
709
- }
710
- };
711
- const normalizeDestinationPath = (path) => path.replaceAll("\\", "/").replace(/^\.\//, "").replaceAll(/\/+/g, "/");
712
- const flattenFiles = (root, path, value) => {
713
- const segments = path.split("/").filter(Boolean);
714
- if (segments.length === 0) {
715
- throw new Error(`Empty destination path for value`);
716
- }
717
- let cursor = root;
718
- for (let index = 0; index < segments.length - 1; index += 1) {
719
- const segment = segments[index];
720
- const existing = cursor[segment];
721
- if (existing === void 0) {
722
- const created = {};
723
- cursor[segment] = created;
724
- cursor = created;
725
- } else if (typeof existing === "object" && !Buffer.isBuffer(existing)) {
726
- cursor = existing;
727
- } else {
728
- throw new TypeError(`Path conflict: "${segments.slice(0, index + 1).join("/")}" is both a file and a directory`);
729
- }
730
- }
731
- cursor[segments.at(-1)] = value;
732
- };
733
- const evaluateGate = (value, scope, source) => {
734
- if (typeof value !== "string") {
735
- return truthyValue(value);
736
- }
737
- const rendered = value.includes("{{") || value.includes("{%") ? renderTemplate(value, { filename: `${source}#frontmatter`, scope }) : value;
738
- return evaluateConditionExpression(rendered, scope, `${source}#frontmatter`, 1);
739
- };
740
- const truthyValue = (value) => {
741
- if (value === null || value === void 0 || value === false || value === 0 || value === "") {
742
- return false;
743
- }
744
- if (Array.isArray(value)) {
745
- return value.length > 0;
746
- }
747
- if (typeof value === "string") {
748
- return value !== "false" && value !== "0";
749
- }
750
- return true;
751
- };
752
- const loadMoonTemplate = (templateDir, name) => {
753
- const yamlPath = join(templateDir, TEMPLATE_FILENAME);
754
- const config = readYamlSync(yamlPath);
755
- if (!config || typeof config !== "object") {
756
- throw new Error(`${yamlPath}: must contain a YAML mapping`);
757
- }
758
- const options = {};
759
- if (config.variables) {
760
- for (const [key, value] of Object.entries(config.variables)) {
761
- options[key] = yamlVariableToVariable(value);
762
- }
763
- }
764
- const partialMap = /* @__PURE__ */ new Map();
765
- const files = [];
766
- for (const entry of walkSync(templateDir, { includeDirs: false, includeSymlinks: false })) {
767
- if (entry.path === yamlPath) {
768
- continue;
769
- }
770
- const relativePath = relative(templateDir, entry.path).replaceAll("\\", "/");
771
- if (relativePath === TEMPLATE_FILENAME) {
772
- continue;
773
- }
774
- if (isPartialPath(relativePath)) {
775
- const source = readFileSync(entry.path);
776
- const parsed = parseTemplate(source, relativePath);
777
- for (const key of partialKeys(relativePath)) {
778
- if (partialMap.has(key)) {
779
- const existing = partialMap.get(key);
780
- if (existing !== parsed) {
781
- console.warn(
782
- `warn: partial name "${key}" is declared by multiple files — last one wins. Use distinct names or fully-qualified includes.`
783
- );
784
- }
785
- }
786
- partialMap.set(key, parsed);
787
- }
788
- continue;
789
- }
790
- const isRaw = relativePath.endsWith(RAW_SUFFIX);
791
- if (isRaw) {
792
- const source = readFileSync(entry.path);
793
- files.push({ binary: null, isRaw: true, rawText: source, relativePath });
794
- continue;
795
- }
796
- if (isLikelyText(relativePath)) {
797
- const source = readFileSync(entry.path);
798
- files.push({ binary: null, isRaw: false, rawText: source, relativePath });
799
- continue;
800
- }
801
- const buffer = readFileSync(entry.path, { buffer: true });
802
- files.push({ binary: buffer, isRaw: false, rawText: null, relativePath });
803
- }
804
- const produce = (context) => {
805
- const scope = { ...context.builtins, ...context.options };
806
- const tree = {};
807
- const meta = {};
808
- for (const file of files) {
809
- const sourcePath = file.relativePath;
810
- let body;
811
- let frontmatterTo;
812
- let frontmatterForce = false;
813
- if (file.binary) {
814
- body = file.binary;
815
- } else if (file.isRaw) {
816
- body = file.rawText ?? "";
817
- } else {
818
- const original = file.rawText ?? "";
819
- const split = splitFrontmatter(original, (yaml) => parse$1(yaml));
820
- if (split.frontmatter) {
821
- if (split.frontmatter.skip !== void 0 && evaluateGate(split.frontmatter.skip, scope, sourcePath)) {
822
- continue;
823
- }
824
- if (split.frontmatter.if !== void 0 && !evaluateGate(split.frontmatter.if, scope, sourcePath)) {
825
- continue;
826
- }
827
- if (typeof split.frontmatter.to === "string") {
828
- frontmatterTo = renderTemplate(split.frontmatter.to, {
829
- filename: `${sourcePath}#frontmatter.to`,
830
- partials: partialMap,
831
- scope
832
- });
833
- }
834
- if (split.frontmatter.force === true) {
835
- frontmatterForce = true;
836
- }
837
- }
838
- body = renderTemplate(split.body, { filename: sourcePath, partials: partialMap, scope });
839
- }
840
- const destinationPath = normalizeDestinationPath(frontmatterTo ?? interpolateFilename(stripRawSuffix(sourcePath), scope));
841
- flattenFiles(tree, destinationPath, body);
842
- if (frontmatterForce) {
843
- meta[destinationPath] = { force: true };
844
- }
845
- }
846
- const creation = { files: tree };
847
- if (Object.keys(meta).length > 0) {
848
- creation.filesMeta = meta;
849
- }
850
- return creation;
851
- };
852
- return {
853
- about: { description: config.description ?? `Moon-format template at ${templateDir}`, name: config.title ?? name },
854
- destination: config.destination,
855
- options,
856
- produce
857
- };
858
- };
859
- const partialKeys = (relativePath) => {
860
- const withoutSuffix = stripTeraSuffix(relativePath);
861
- const keys = /* @__PURE__ */ new Set();
862
- keys.add(withoutSuffix);
863
- const base = basename(withoutSuffix);
864
- keys.add(base);
865
- if (base.startsWith("_")) {
866
- keys.add(base.replace(/^_+/, ""));
867
- const directory = withoutSuffix.slice(0, withoutSuffix.length - base.length);
868
- keys.add(`${directory}${base.replace(/^_+/, "")}`);
869
- }
870
- return [...keys];
871
- };
872
-
873
- export { loadMoonTemplate };
1
+ var z=Object.defineProperty;var v=(e,t)=>z(e,"name",{value:t,configurable:!0});import{walkSync as H,readFileSync as k}from"@visulima/fs";import{readYamlSync as X}from"@visulima/fs/yaml";import{relative as M,join as N}from"@visulima/path";import{p as Z,ao as ee}from"./bin.js";import{constantCase as te,trainCase as ne,snakeCase as re,pascalCase as ie,kebabCase as se,camelCase as ae}from"@visulima/string/case";var oe=Object.defineProperty,d=v((e,t)=>oe(e,"name",{value:t,configurable:!0}),"e");const h=d(e=>e==null?"":typeof e=="string"?e:typeof e=="number"||typeof e=="boolean"||typeof e=="bigint"?String(e):JSON.stringify(e),"stringify"),S={camel_case:d(e=>ae(h(e)),"camel_case"),kebab_case:d(e=>se(h(e)),"kebab_case"),lower_case:d(e=>h(e).toLowerCase(),"lower_case"),pascal_case:d(e=>ie(h(e)),"pascal_case"),path_join:d((e,...t)=>{const r=[h(e),...t.map(n=>h(n))];return N(...r)},"path_join"),path_relative:d((e,t)=>M(h(t),h(e)),"path_relative"),snake_case:d(e=>re(h(e)),"snake_case"),upper_case:d(e=>h(e).toUpperCase(),"upper_case"),upper_kebab_case:d(e=>ne(h(e)),"upper_kebab_case"),upper_snake_case:d(e=>te(h(e)),"upper_snake_case")};d(e=>Object.hasOwn(S,e),"isKnownFilter");const R=d((e,t,r=[])=>{const n=S[e];if(!n)throw new Error(`Unknown filter "${e}". Known filters: ${Object.keys(S).sort().join(", ")}.`);return n(t,...r)},"applyFilter");var le=Object.defineProperty,I=v((e,t)=>le(e,"name",{value:t,configurable:!0}),"d$1");const L=I(e=>{const t=[];let r=0,n=0,i,s=-1;for(const a of e){if(s+=1,i){a===i&&(i=void 0);continue}if(a==='"'||a==="'"){i=a;continue}a==="("?r+=1:a===")"?r-=1:a===","&&r===0&&(t.push(e.slice(n,s)),n=s+1)}return t.push(e.slice(n)),t},"splitCommaOutsideQuotes"),ce=I(e=>{if(e.startsWith('"')&&e.endsWith('"')||e.startsWith("'")&&e.endsWith("'"))return e.slice(1,-1)},"stripQuotes");var fe=Object.defineProperty,$=v((e,t)=>fe(e,"name",{value:t,configurable:!0}),"s$1");const ue=/\[([^\]]+)\]/g,pe=$(e=>{const t=e.trim(),r=t.indexOf("(");if(r===-1)return{args:[],name:t};if(!t.endsWith(")"))throw new Error(`Filter call "${e}" missing closing ")"`);const n=t.slice(0,r).trim(),i=t.slice(r+1,-1).trim();return i===""?{args:[],name:n}:{args:L(i).map(s=>{const a=s.trim();return ce(a)??a}),name:n}},"parseFilterCall"),me=$(e=>{const t=e.split("|").map(n=>n.trim()),r=t[0]??"";if(!r)throw new Error(`Empty variable name in expression "${e}"`);return{filters:t.slice(1).map(n=>pe(n)),name:r}},"parsePipe"),de=$(e=>e==null?"":typeof e=="string"?e:typeof e=="number"||typeof e=="boolean"||typeof e=="bigint"?String(e):JSON.stringify(e),"stringify"),he=$((e,t)=>{const r=e.replaceAll(ue,(n,i)=>{const{filters:s,name:a}=me(i);if(!Object.hasOwn(t,a))throw new Error(`Variable "${a}" used in filename "${e}" but not defined`);let o=t[a];for(const l of s)o=R(l.name,o,l.args);return de(o)});return q(r)},"interpolateFilename"),q=$(e=>e.split("/").map(t=>t.endsWith(".tera")||t.endsWith(".twig")?t.slice(0,-5):t).join("/"),"stripTeraSuffix"),ye=$(e=>e.endsWith(".raw")?e.slice(0,-4):e,"stripRawSuffix"),ge=$(e=>{const t=e.split("/");return(t.at(-1)??"").startsWith("_")?!0:t.slice(0,-1).includes("partials")},"isPartialPath");var be=Object.defineProperty,we=v((e,t)=>be(e,"name",{value:t,configurable:!0}),"i");const ve=we((e,t)=>{if(!e.startsWith(`---
2
+ `)&&!e.startsWith(`---\r
3
+ `))return{body:e};const r=e.indexOf(`
4
+ ---`,3);if(r===-1)return{body:e};let n=r+1+3;e[n]==="\r"&&(n+=1),e[n]===`
5
+ `&&(n+=1);const i=e.startsWith(`---\r
6
+ `)?5:4,s=e.slice(i,r).replaceAll("\r","");let a;try{a=t(s)}catch(o){const l=o instanceof Error?o.message:String(o);throw new Error(`Failed to parse frontmatter YAML: ${l}`,{cause:o})}if(a==null)return{body:e.slice(n)};if(typeof a!="object"||Array.isArray(a))throw new TypeError(`Frontmatter must be a YAML mapping, got ${Array.isArray(a)?"array":typeof a}`);return{body:e.slice(n),frontmatter:a}},"splitFrontmatter");var $e=Object.defineProperty,f=v((e,t)=>$e(e,"name",{value:t,configurable:!0}),"c");const xe=/\{\{-?(.+?)-?\}\}|\{%-?(.+?)-?%\}/gs,U=f(e=>{const t=[];let r=0,n=1;const i=f(s=>{for(const a of s)a===`
7
+ `&&(n+=1)},"advanceLine");for(const s of e.matchAll(xe)){const a=s.index??0;if(a>r){const c=e.slice(r,a);t.push({line:n,type:"text",value:c}),i(c)}const o=n,l=s[0];l.startsWith("{{")?t.push({line:o,type:"expr",value:(s[1]??"").trim()}):t.push({line:o,type:"stmt",value:(s[2]??"").trim()}),i(l),r=a+l.length}if(r<e.length){const s=e.slice(r);t.push({line:n,type:"text",value:s})}return t},"tokenize"),Ee=new Set(["block","endblock","endfilter","endmacro","extends","filter","import","macro","set"]),Q=f((e,t)=>{let r=0;const n=f((s,a)=>{throw new Error(`${t}:${s}: ${a}`)},"error"),i=f(s=>{const a=[];for(;r<e.length;){const o=e[r];if(o.type==="text"){a.push({type:"text",value:o.value}),r+=1;continue}if(o.type==="expr"){a.push({expression:o.value,line:o.line,type:"expr"}),r+=1;continue}const l=o.value.split(/\s+/)[0]??"";if(s.includes(l))return a;if(Ee.has(l)&&n(o.line,`Tera feature "{% ${l} %}" is not supported. Supported: if/else/endif, for/endfor, include. Rewrite the template to avoid macros, set, extends, block, and import.`),l==="if"){r+=1;const c=o.value.slice(2).trim(),m=i(["else","endif"]);let u;r<e.length&&e[r].type==="stmt"&&e[r].value.split(/\s+/)[0]==="else"&&(r+=1,u=i(["endif"])),(r>=e.length||e[r].type!=="stmt"||e[r].value.split(/\s+/)[0]!=="endif")&&n(o.line,"Unterminated {% if %} — missing {% endif %}"),r+=1,a.push({alternate:u,condition:c,consequent:m,line:o.line,type:"if"});continue}if(l==="for"){r+=1;const c=o.value.slice(3).trim(),m=c.indexOf(" in ");m===-1&&n(o.line,"Malformed {% for %} — expected `for <name> in <collection>`");const u=c.slice(0,m).trim(),g=c.slice(m+4).trim();(!u||!g)&&n(o.line,"Malformed {% for %} — missing binding or collection");const p=i(["endfor"]);(r>=e.length||e[r].type!=="stmt"||e[r].value.split(/\s+/)[0]!=="endfor")&&n(o.line,"Unterminated {% for %} — missing {% endfor %}"),r+=1,a.push({binding:u,body:p,collection:g,line:o.line,type:"for"});continue}if(l==="include"){r+=1;const c=o.value.slice(7).trim(),m=G(c);m===void 0&&n(o.line,"Malformed {% include %} — expected a quoted partial name"),a.push({line:o.line,name:m,type:"include"});continue}n(o.line,`Unknown tag "{% ${l} %}". Supported: if, for, include.`)}return a},"parseBlock");return i([])},"parse"),G=f(e=>{if(e.startsWith('"')&&e.endsWith('"')||e.startsWith("'")&&e.endsWith("'"))return e.slice(1,-1)},"stripQuotes"),Oe=f(e=>e==null?"":typeof e=="boolean"||typeof e=="number"?String(e):typeof e=="string"?e:JSON.stringify(e),"stringify"),ke=f(e=>e==null||e===!1||e===0||e===""?!1:Array.isArray(e)?e.length>0:!0,"isTruthy"),V=f((e,t,r,n,i=!0)=>{const s=e.split(".");if(i&&s.length>0&&!Object.hasOwn(t,s[0]))throw new Error(`${n}:${r}: Variable "${e}" is not defined`);let a=t;for(const o of s){if(a==null)return;if(typeof a!="object")throw new TypeError(`${n}:${r}: Cannot read "${o}" on non-object value`);a=a[o]}return a},"lookupVariable"),je=f(e=>{const t=[];let r=0,n=0,i;for(let s=0;s<e.length;s+=1){const a=e[s];if(i){a===i&&(i=void 0);continue}if(a==='"'||a==="'"){i=a;continue}a==="("?r+=1:a===")"?r-=1:a==="|"&&r===0&&(t.push(e.slice(n,s)),n=s+1)}return t.push(e.slice(n)),t},"splitFilterPipe"),Se=f(e=>{const t=e.trim(),r=t.indexOf("(");if(r===-1)return{args:[],name:t};if(!t.endsWith(")"))throw new Error(`Filter call "${e}" missing closing ")"`);const n=t.slice(0,r).trim(),i=t.slice(r+1,-1).trim();return i?{args:L(i).map(s=>s.trim()),name:n}:{args:[],name:n}},"parseFilterCall"),j=f((e,t,r,n,i=!0)=>{const s=e.trim();if(s==="true")return!0;if(s==="false")return!1;if(s==="null"||s==="none")return null;const a=G(s);return a!==void 0?a:/^-?\d+(?:\.\d+)?$/.test(s)?Number(s):V(s,t,r,n,i)},"evaluatePrimary"),B=f((e,t,r,n,i=!0)=>{const s=je(e),a=s[0].trim();let o=j(a,t,r,n,i);for(let l=1;l<s.length;l+=1){const c=s[l].trim(),{args:m,name:u}=Se(c),g=m.map(p=>j(p,t,r,n,i));try{o=R(u,o,g)}catch(p){const w=p instanceof Error?p.message:String(p);throw new Error(`${n}:${r}: ${w}`,{cause:p})}}return o},"evaluateExpression"),D=f((e,t)=>{let r,n=0;for(let i=0;i<=e.length-t.length;i+=1){const s=e[i];if(r){s===r&&(r=void 0);continue}if(s==='"'||s==="'"){r=s;continue}if(s==="("){n+=1;continue}if(s===")"){n-=1;continue}if(n===0&&e.slice(i,i+t.length)===t)return i}return-1},"findOperatorOutsideQuotes"),A=f((e,t)=>{const r=[];let n=0,i=0;for(;i<=e.length-t.length;){const s=D(e.slice(i),t);if(s===-1)break;r.push(e.slice(n,i+s)),i+=s+t.length,n=i}return r.push(e.slice(n)),r},"splitLogical"),x=f((e,t,r,n)=>{const i=e.trim();if(i==="")return!1;if(i.startsWith("(")&&i.endsWith(")")&&Ce(i)===i.length-1)return x(i.slice(1,-1),t,r,n);const s=A(i," or ");if(s.length>1)return s.some(o=>x(o,t,r,n));const a=A(i," and ");if(a.length>1)return a.every(o=>x(o,t,r,n));if(i.startsWith("not "))return!x(i.slice(4),t,r,n);for(const o of["==","!="]){const l=D(i,o);if(l!==-1){const c=j(i.slice(0,l),t,n,r,!1),m=j(i.slice(l+o.length),t,n,r,!1),u=c===m;return o==="=="?u:!u}}return ke(B(i,t,n,r,!1))},"evaluateConditionExpression"),Ce=f(e=>{if(e[0]!=="(")return-1;let t=0,r,n=-1;for(const i of e){if(n+=1,r){i===r&&(r=void 0);continue}if(i==='"'||i==="'"){r=i;continue}if(i==="(")t+=1;else if(i===")"&&(t-=1,t===0))return n}return-1},"matchingParen"),E=f((e,t)=>{let r="";for(const n of e){if(n.type==="text"){r+=n.value;continue}if(n.type==="expr"){r+=Oe(B(n.expression,t.scope,n.line,t.filename));continue}if(n.type==="if"){const i=x(n.condition,t.scope,t.filename,n.line)?n.consequent:n.alternate??[];r+=E(i,t);continue}if(n.type==="for"){const i=V(n.collection,t.scope,n.line,t.filename,!1);if(i==null)continue;if(!Array.isArray(i))throw new TypeError(`${t.filename}:${n.line}: {% for %} expected an array, got ${typeof i}`);for(const s of i){const a={...t.scope,[n.binding]:s};r+=E(n.body,{...t,scope:a})}continue}if(n.type==="include"){const i=t.partials?.get(n.name);if(!i)throw new Error(`${t.filename}:${n.line}: Partial "${n.name}" not found. Available: ${[...t.partials?.keys()??[]].join(", ")||"(none)"}`);const s=t.includeStack??new Set;if(s.has(n.name)){const a=[...s,n.name].join(" → ");throw new Error(`${t.filename}:${n.line}: Circular partial include detected: ${a}`)}s.add(n.name);try{r+=E(i,{...t,filename:`<partial:${n.name}>`,includeStack:s})}finally{s.delete(n.name)}}}return r},"renderNodes"),_e=f((e,t)=>Q(U(e),t),"parseTemplate");f((e,t)=>E(e,t),"renderAst");const C=f((e,t)=>E(Q(U(e),t.filename),t),"renderTemplate");var Te=Object.defineProperty,y=v((e,t)=>Te(e,"name",{value:t,configurable:!0}),"s");const P="template.yml",We=".raw",Ae=new Set([".cjs",".css",".env",".gitattributes",".gitignore",".graphql",".html",".ini",".js",".json",".json5",".jsonc",".jsx",".lock",".md",".mdx",".mjs",".prettierrc",".raw",".rs",".scss",".sh",".sql",".svg",".tera",".toml",".ts",".tsx",".twig",".txt",".vue",".xml",".yaml",".yml"]),Pe=new Set(["Brewfile","CHANGELOG","CODEOWNERS","CONTRIBUTING","COPYING","Dockerfile","Gemfile","LICENCE","LICENSE","Makefile","NOTICE","Procfile","Rakefile","README","VERSION"]),Fe=y(e=>{const t=Math.max(e.lastIndexOf("/"),e.lastIndexOf("\\")),r=t===-1?e:e.slice(t+1),n=r.lastIndexOf(".");return n===-1||n===0?"":r.slice(n).toLowerCase()},"lastDot"),Y=y(e=>{const t=Math.max(e.lastIndexOf("/"),e.lastIndexOf("\\"));return t===-1?e:e.slice(t+1)},"basename"),Me=y(e=>{const t=Fe(e);if(t!=="")return Ae.has(t);const r=Y(e);return Pe.has(r)},"isLikelyText"),Ne=y(e=>{const t={default:e.default,internal:e.internal,order:e.order,prompt:e.prompt,required:e.required};switch(e.type){case"array":return{...t,type:"array"};case"boolean":return{...t,default:typeof t.default=="boolean"?t.default:void 0,type:"boolean"};case"enum":return{...t,multiple:e.multiple,type:"enum",values:e.values??[]};case"number":return{...t,type:"number"};case"string":return{...t,type:"string"};default:throw new Error(`Unsupported variable type "${String(e.type)}"`)}},"yamlVariableToVariable"),Re=y(e=>e.replaceAll("\\","/").replace(/^\.\//,"").replaceAll(/\/+/g,"/"),"normalizeDestinationPath"),Ie=y((e,t,r)=>{const n=t.split("/").filter(Boolean);if(n.length===0)throw new Error("Empty destination path for value");let i=e;for(let s=0;s<n.length-1;s+=1){const a=n[s],o=i[a];if(o===void 0){const l={};i[a]=l,i=l}else if(typeof o=="object"&&!Buffer.isBuffer(o))i=o;else throw new TypeError(`Path conflict: "${n.slice(0,s+1).join("/")}" is both a file and a directory`)}i[n.at(-1)]=r},"flattenFiles"),F=y((e,t,r)=>{if(typeof e!="string")return Le(e);const n=e.includes("{{")||e.includes("{%")?C(e,{filename:`${r}#frontmatter`,scope:t}):e;return x(n,t,`${r}#frontmatter`,1)},"evaluateGate"),Le=y(e=>e==null||e===!1||e===0||e===""?!1:Array.isArray(e)?e.length>0:typeof e=="string"?e!=="false"&&e!=="0":!0,"truthyValue"),Ye=y((e,t)=>{const r=N(e,P),n=X(r);if(!n||typeof n!="object")throw new Error(`${r}: must contain a YAML mapping`);const i={};if(n.variables)for(const[l,c]of Object.entries(n.variables))i[l]=Ne(c);const s=new Map,a=[];for(const l of H(e,{includeDirs:!1,includeSymlinks:!1})){if(l.path===r)continue;const c=M(e,l.path).replaceAll("\\","/");if(c===P)continue;if(ge(c)){const u=k(l.path),g=_e(u,c);for(const p of qe(c))s.has(p)&&s.get(p)!==g&&Z.warn(`partial name "${p}" is declared by multiple files — last one wins. Use distinct names or fully-qualified includes.`),s.set(p,g);continue}if(c.endsWith(We)){const u=k(l.path);a.push({binary:null,isRaw:!0,rawText:u,relativePath:c});continue}if(Me(c)){const u=k(l.path);a.push({binary:null,isRaw:!1,rawText:u,relativePath:c});continue}const m=k(l.path,{buffer:!0});a.push({binary:m,isRaw:!1,rawText:null,relativePath:c})}const o=y(l=>{const c={...l.builtins,...l.options},m={},u={};for(const p of a){const w=p.relativePath;let O,_,T=!1;if(p.binary)O=p.binary;else if(p.isRaw)O=p.rawText??"";else{const J=p.rawText??"",b=ve(J,K=>ee(K));if(b.frontmatter){if(b.frontmatter.skip!==void 0&&F(b.frontmatter.skip,c,w)||b.frontmatter.if!==void 0&&!F(b.frontmatter.if,c,w))continue;typeof b.frontmatter.to=="string"&&(_=C(b.frontmatter.to,{filename:`${w}#frontmatter.to`,partials:s,scope:c})),b.frontmatter.force===!0&&(T=!0)}O=C(b.body,{filename:w,partials:s,scope:c})}const W=Re(_??he(ye(w),c));Ie(m,W,O),T&&(u[W]={force:!0})}const g={files:m};return Object.keys(u).length>0&&(g.filesMeta=u),g},"produce");return{about:{description:n.description??`Moon-format template at ${e}`,name:n.title??t},destination:n.destination,options:i,produce:o}},"loadMoonTemplate"),qe=y(e=>{const t=q(e),r=Y(t),n=new Set([r,t]);if(r.startsWith("_")){n.add(r.replace(/^_+/,""));const i=t.slice(0,t.length-r.length);n.add(`${i}${r.replace(/^_+/,"")}`)}return[...n]},"partialKeys");export{Ye as loadMoonTemplate};