mutorjs 1.1.1 → 1.3.0

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.
package/dist/cli.cjs ADDED
@@ -0,0 +1,1783 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/bin/cli.ts
22
+ var cli_exports = {};
23
+ __export(cli_exports, {
24
+ handleBuildCommand: () => handleBuildCommand,
25
+ handleCompileCommand: () => handleCompileCommand,
26
+ handleRenderCommand: () => handleRenderCommand,
27
+ parseArgs: () => parseArgs,
28
+ safeParseJsonFile: () => safeParseJsonFile,
29
+ safeReadFile: () => safeReadFile,
30
+ safeWriteFile: () => safeWriteFile
31
+ });
32
+ module.exports = __toCommonJS(cli_exports);
33
+ var import_node_fs2 = require("fs");
34
+ var import_node_process = require("process");
35
+
36
+ // package.json
37
+ var version = "1.3.0";
38
+
39
+ // src/core/mutor.server.ts
40
+ var import_node_fs = require("fs");
41
+ var import_promises = require("fs/promises");
42
+ var import_node_path2 = require("path");
43
+
44
+ // src/utils/construct-pointer.ts
45
+ function constructPointer(pos, offset) {
46
+ return `${" ".repeat(pos + offset + 1)}^`;
47
+ }
48
+
49
+ // src/core/error.ts
50
+ var MutorError = class _MutorError extends Error {
51
+ constructor(message) {
52
+ super(message);
53
+ this.name = "MutorError";
54
+ Object.setPrototypeOf(this, _MutorError.prototype);
55
+ }
56
+ };
57
+ var MutorCompilerError = class _MutorCompilerError extends MutorError {
58
+ constructor(message, line, lineText, column, file) {
59
+ const gutterWidth = line.toString().length + 2;
60
+ let report = `${message}
61
+
62
+ `;
63
+ report += `at ${file}:${line}:${column + 1}
64
+ `;
65
+ if (line > 1) {
66
+ report += `${(line - 1).toString().padStart(gutterWidth - 2)} | ...
67
+ `;
68
+ }
69
+ report += `${line} | ${lineText}
70
+ `;
71
+ report += constructPointer(column, gutterWidth);
72
+ super(report);
73
+ this.name = "MutorCompilerError";
74
+ Object.setPrototypeOf(this, _MutorCompilerError.prototype);
75
+ }
76
+ };
77
+
78
+ // src/core/constants.ts
79
+ var keywords = /* @__PURE__ */ new Set([
80
+ "for",
81
+ "if",
82
+ "else",
83
+ "true",
84
+ "false",
85
+ "null",
86
+ "undefined",
87
+ "end",
88
+ "in",
89
+ "of"
90
+ ]);
91
+ var operators = /* @__PURE__ */ new Set([
92
+ "::",
93
+ // Namespace access
94
+ "||",
95
+ // Or
96
+ "??",
97
+ // Nullish coalesce
98
+ "&&",
99
+ // And
100
+ "**",
101
+ // Power
102
+ "^",
103
+ // Bitwise XOr
104
+ "|",
105
+ // Bitwise Or
106
+ "&",
107
+ // Bitwise And
108
+ "!",
109
+ // Not
110
+ "-",
111
+ // Minus
112
+ "%",
113
+ // Modulus
114
+ "+",
115
+ // Plus
116
+ "*",
117
+ // Times
118
+ "/",
119
+ // Divide
120
+ ">",
121
+ // Greater than
122
+ "<",
123
+ // Less than
124
+ ">=",
125
+ // Greater or equal
126
+ "<=",
127
+ // Less or equal
128
+ "==",
129
+ // Strict equal
130
+ "!=",
131
+ // Strict not equal
132
+ ">>",
133
+ // Bitwise right shift
134
+ "<<",
135
+ // Bitwise left shift
136
+ ".",
137
+ // Property acess
138
+ "?.",
139
+ // Optional property access
140
+ "(",
141
+ // Open parentheses
142
+ ")",
143
+ // Close parentheses
144
+ "[",
145
+ // Square open parentheses
146
+ "]",
147
+ // Square close parentheses
148
+ ",",
149
+ // Comma
150
+ ":",
151
+ // Column
152
+ "?"
153
+ // Ternary operator
154
+ ]);
155
+ var equalityOperators = /* @__PURE__ */ new Set(["==", "!="]);
156
+ var comparisonOperators = /* @__PURE__ */ new Set([">", "<", ">=", "<="]);
157
+ var additiveOperators = /* @__PURE__ */ new Set(["+", "-"]);
158
+ var multiplicativeOperators = /* @__PURE__ */ new Set(["*", "/", "%"]);
159
+ var propertyAccessOperators = /* @__PURE__ */ new Set([".", "?.", "[", "::"]);
160
+ var unaryOperators = /* @__PURE__ */ new Set(["-", "+", "!"]);
161
+ var ESCAPE_MAP = {
162
+ "&": "&amp;",
163
+ "<": "&lt;",
164
+ ">": "&gt;",
165
+ '"': "&quot;",
166
+ "'": "&#39;"
167
+ };
168
+ var defaultConfig = {
169
+ build: {
170
+ include: /* @__PURE__ */ new Set([".html", ".txt"]),
171
+ exclude: /* @__PURE__ */ new Set(["node_modules", ".git"])
172
+ },
173
+ autoEscape: true,
174
+ allowedProps: /* @__PURE__ */ new Set(),
175
+ forbiddenProps: /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]),
176
+ allowFnCalls: false,
177
+ delimiters: {
178
+ closingTag: "}}",
179
+ openingTag: "{{",
180
+ openingTagEscape: "\\",
181
+ whitespaceTrim: "~",
182
+ commentTag: "#"
183
+ },
184
+ keepOpeningTagEscapeDelimiter: false,
185
+ cache: {
186
+ active: true,
187
+ maxSize: 50 * 1024 * 1024
188
+ // 50MB
189
+ }
190
+ };
191
+ var namespaces = {
192
+ JSON: {
193
+ stringify(value) {
194
+ try {
195
+ return JSON.stringify(value);
196
+ } catch {
197
+ throw new MutorError("JSON.stringify failed: invalid value");
198
+ }
199
+ },
200
+ parse(str) {
201
+ if (typeof str !== "string") {
202
+ throw new MutorError("JSON.parse expects a string");
203
+ }
204
+ try {
205
+ return JSON.parse(str);
206
+ } catch {
207
+ throw new MutorError("JSON.parse failed: invalid JSON string");
208
+ }
209
+ }
210
+ },
211
+ Object: {
212
+ keys(obj) {
213
+ if (!obj || typeof obj !== "object") {
214
+ throw new MutorError("Object.keys expects an object");
215
+ }
216
+ return Object.keys(obj);
217
+ },
218
+ values(obj) {
219
+ if (!obj || typeof obj !== "object") {
220
+ throw new MutorError("Object.values expects an object");
221
+ }
222
+ return Object.values(obj);
223
+ },
224
+ entries(obj) {
225
+ if (!obj || typeof obj !== "object") {
226
+ throw new MutorError("Object.entries expects an object");
227
+ }
228
+ return Object.entries(obj);
229
+ },
230
+ hasOwn(obj, key) {
231
+ if (!obj || typeof obj !== "object") {
232
+ throw new MutorError("Object.hasOwn expects an object");
233
+ }
234
+ return Object.hasOwn(obj, key);
235
+ },
236
+ freeze(obj) {
237
+ if (!obj || typeof obj !== "object") {
238
+ throw new MutorError("Object.freeze expects an object");
239
+ }
240
+ return Object.freeze(obj);
241
+ },
242
+ seal(obj) {
243
+ if (!obj || typeof obj !== "object") {
244
+ throw new MutorError("Object.seal expects an object");
245
+ }
246
+ return Object.seal(obj);
247
+ },
248
+ fromEntries(entries) {
249
+ if (!Array.isArray(entries)) {
250
+ throw new MutorError("Object.fromEntries expects an array");
251
+ }
252
+ return Object.fromEntries(entries);
253
+ }
254
+ },
255
+ Array: {
256
+ isArray(value) {
257
+ return Array.isArray(value);
258
+ },
259
+ from(value) {
260
+ return Array.from(value);
261
+ }
262
+ },
263
+ Number: {
264
+ isFinite(value) {
265
+ return Number.isFinite(value);
266
+ },
267
+ isNaN(value) {
268
+ return Number.isNaN(value);
269
+ },
270
+ parseInt(value, radix = 10) {
271
+ return Number.parseInt(value, radix);
272
+ },
273
+ parseFloat(value) {
274
+ return Number.parseFloat(value);
275
+ }
276
+ },
277
+ String: {
278
+ fromCharCode(...args) {
279
+ return String.fromCharCode(...args);
280
+ }
281
+ },
282
+ Math: {
283
+ abs(x) {
284
+ return Math.abs(x);
285
+ },
286
+ floor(x) {
287
+ return Math.floor(x);
288
+ },
289
+ ceil(x) {
290
+ return Math.ceil(x);
291
+ },
292
+ round(x) {
293
+ return Math.round(x);
294
+ },
295
+ max(...args) {
296
+ return Math.max(...args);
297
+ },
298
+ min(...args) {
299
+ return Math.min(...args);
300
+ },
301
+ random() {
302
+ return Math.random();
303
+ }
304
+ },
305
+ Date: {
306
+ now() {
307
+ return Date.now();
308
+ },
309
+ parse(str) {
310
+ if (typeof str !== "string") {
311
+ throw new MutorError("Date.parse expects a string");
312
+ }
313
+ return Date.parse(str);
314
+ }
315
+ },
316
+ Boolean: {
317
+ valueOf(value) {
318
+ return Boolean(value);
319
+ }
320
+ }
321
+ };
322
+
323
+ // src/utils/escape-fn.ts
324
+ function escapeFn(e) {
325
+ if (typeof e !== "string") return e;
326
+ return /[&<>"']/.test(e) ? e.replace(/[&<>"']/g, (char) => ESCAPE_MAP[char]) : e;
327
+ }
328
+
329
+ // src/utils/to-absolute-path.ts
330
+ var import_node_path = require("path");
331
+ function toAbsolutePath(basePath, ...relativePaths) {
332
+ const absoluteBase = (0, import_node_path.isAbsolute)(basePath) ? basePath : (0, import_node_path.resolve)(process.cwd(), basePath);
333
+ if (relativePaths.length) {
334
+ const baseDir = (0, import_node_path.dirname)(absoluteBase);
335
+ return (0, import_node_path.resolve)(baseDir, ...relativePaths);
336
+ }
337
+ return absoluteBase;
338
+ }
339
+
340
+ // src/utils/validate-computed-prop.ts
341
+ function validateComputedProp(r, allowedProps, forbiddenProps) {
342
+ if (forbiddenProps.has(r) && !allowedProps.has(r)) {
343
+ throw new MutorError(
344
+ `Forbidden property access. Access to this computed property "${r}" is forbidden.`
345
+ );
346
+ }
347
+ return r;
348
+ }
349
+
350
+ // src/utils/validate-context.ts
351
+ var OBJECT = "object";
352
+ var MUTOR_SAFE = /* @__PURE__ */ Symbol("__mutor_safe_context");
353
+ function validateContext(ctx) {
354
+ if (!ctx || typeof ctx !== OBJECT) {
355
+ return ctx;
356
+ }
357
+ if (MUTOR_SAFE in ctx) {
358
+ return ctx;
359
+ }
360
+ const seen = /* @__PURE__ */ new WeakSet();
361
+ function walk(value, path = "") {
362
+ if (!value || typeof value !== OBJECT) return value;
363
+ if (seen.has(value)) return value;
364
+ seen.add(value);
365
+ const proto = Object.getPrototypeOf(value);
366
+ if (proto && proto !== Object.prototype && proto !== Array.prototype) {
367
+ throw new MutorError(`Unsafe prototype detected at ${path || "root"}`);
368
+ }
369
+ if (Array.isArray(value)) {
370
+ for (let i = 0; i < value.length; i++) {
371
+ value[i] = walk(value[i], `${path}[${i}]`);
372
+ }
373
+ return value;
374
+ }
375
+ if (value instanceof Map) {
376
+ for (const [k, v] of value.entries()) {
377
+ if (typeof k === OBJECT) walk(k, `${path}.mapKey`);
378
+ value.set(k, walk(v, `${path}.mapValue`));
379
+ }
380
+ return value;
381
+ }
382
+ if (value instanceof Set) {
383
+ const next = /* @__PURE__ */ new Set();
384
+ for (const v of value.values()) {
385
+ next.add(walk(v, path));
386
+ }
387
+ value.clear();
388
+ for (const v of next) value.add(v);
389
+ return value;
390
+ }
391
+ const descriptors = Object.getOwnPropertyDescriptors(value);
392
+ for (const key of Object.keys(descriptors)) {
393
+ const desc = descriptors[key];
394
+ if (desc.get || desc.set) {
395
+ throw new MutorError(`Getter/setter not allowed: ${path}.${key}`);
396
+ }
397
+ const prop = value[key];
398
+ if (prop && typeof prop === OBJECT) {
399
+ value[key] = walk(prop, `${path}.${key}`);
400
+ }
401
+ }
402
+ return value;
403
+ }
404
+ const safeData = walk(ctx);
405
+ if (safeData && typeof safeData === OBJECT) {
406
+ Object.defineProperty(safeData, MUTOR_SAFE, {
407
+ value: true,
408
+ enumerable: false,
409
+ writable: false,
410
+ configurable: false
411
+ });
412
+ }
413
+ return safeData;
414
+ }
415
+
416
+ // src/utils/escape-raw-text.ts
417
+ function escapeRawText(text) {
418
+ return text.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$");
419
+ }
420
+
421
+ // src/utils/get-line-and-column-nums.ts
422
+ function getLineAndColumnNumbers(str, idx) {
423
+ const lines = str.slice(0, idx).split("\n");
424
+ const line = lines.length;
425
+ const lineIndex = str.lastIndexOf("\n", idx - 1) + 1;
426
+ return { line, lineIndex };
427
+ }
428
+
429
+ // src/utils/get-line-snapshot.ts
430
+ function getLineSnapshot(str, lineIdx, idx) {
431
+ const nextNewlineIdx = str.indexOf("\n", lineIdx);
432
+ const line = str.slice(
433
+ lineIdx,
434
+ nextNewlineIdx === -1 ? void 0 : nextNewlineIdx
435
+ );
436
+ return { line, pos: idx - lineIdx };
437
+ }
438
+
439
+ // src/core/build.ts
440
+ function build(ast, context) {
441
+ const { scope, forbiddenProps, allowedProps } = context;
442
+ function prefixWithCtx(ident) {
443
+ return scope.includes(ident) ? ident : `ctx.${ident}`;
444
+ }
445
+ function buildExpr(expr) {
446
+ switch (expr.type) {
447
+ case 17 /* END */:
448
+ return "}";
449
+ case 5 /* NUMBER */:
450
+ return expr.value;
451
+ case 4 /* STRING */:
452
+ return `\`${/\$/.test(expr.value) ? expr.value.replaceAll("$", "\\$") : expr.value}\``;
453
+ case 10 /* BOOLEAN */:
454
+ return expr.true ? "true" : "false";
455
+ case 12 /* NULL */:
456
+ return "null";
457
+ case 11 /* UNDEFINED */:
458
+ return "undefined";
459
+ case 7 /* IDENT */:
460
+ return prefixWithCtx(expr.value);
461
+ case 9 /* GROUP */:
462
+ return `(${buildExpr(expr.expr)})`;
463
+ case 2 /* UNARY */:
464
+ return `${expr.operator}${buildExpr(expr.expr)}`;
465
+ case 0 /* BINARY */:
466
+ return `${buildExpr(expr.left)} ${expr.operator} ${buildExpr(expr.right)}`;
467
+ case 1 /* TERNARY */:
468
+ return `${buildExpr(expr.condition)} ? ${buildExpr(expr.left)} : ${buildExpr(expr.right)}`;
469
+ case 8 /* PROP_ACCESS */:
470
+ return buildPropAccess(expr);
471
+ case 3 /* CALL */:
472
+ return buildCall(expr);
473
+ case 6 /* NAMESPACE */:
474
+ return buildNamespace(expr);
475
+ case 13 /* FOR */:
476
+ return buildForLoop(expr);
477
+ case 16 /* ELSE */:
478
+ return "} else {";
479
+ case 14 /* IF */:
480
+ return buildIfBlock(expr);
481
+ case 15 /* ELSE_IF */:
482
+ return buildElseIfBlock(expr);
483
+ default:
484
+ throw new MutorError(
485
+ `Unsupported expression type: ${expr.type}`
486
+ );
487
+ }
488
+ }
489
+ function buildNamespace(expr) {
490
+ if (expr.left.type !== 7 /* IDENT */) {
491
+ throw {
492
+ message: "Invalid usage of namespace operator.",
493
+ pos: expr.pos
494
+ };
495
+ }
496
+ return `namespaces.${expr.left.value}.${expr.right.value}`;
497
+ }
498
+ function buildPropAccess(expr) {
499
+ const left = buildExpr(expr.left);
500
+ if (expr.bracketNotation) {
501
+ const right = buildExpr(expr.right);
502
+ const optionalChain = expr.optional ? "?." : "";
503
+ return expr.right.type === 4 /* STRING */ || expr.right.type === 5 /* NUMBER */ ? `${left}${optionalChain}[${right}]` : `${left}${optionalChain}[validateComputedProps(${right}, allowedProps, forbiddenProps)]`;
504
+ } else {
505
+ const propName = expr.right.value;
506
+ const optionalChain = expr.optional ? "?." : ".";
507
+ if (forbiddenProps.has(propName) && !allowedProps.has(propName)) {
508
+ throw { message: "Forbidden property access.", pos: expr.right.pos };
509
+ }
510
+ return `${left}${optionalChain}${propName}`;
511
+ }
512
+ }
513
+ function buildCall(expr) {
514
+ const func = buildExpr(expr.expr);
515
+ const optionalChain = expr.optional ? "?." : "";
516
+ const args = expr.args.map((arg) => buildExpr(arg)).join(", ");
517
+ return `${func}${optionalChain}(${args})`;
518
+ }
519
+ function buildForLoop(expr) {
520
+ const { iterable, loopType, variable } = expr;
521
+ return `for(const ${variable} ${loopType === 1 /* IN */ ? "in" : "of"} ${build(iterable, context)}){`;
522
+ }
523
+ function buildIfBlock(expr) {
524
+ const { condition } = expr;
525
+ return `if(${build(condition, context)}){`;
526
+ }
527
+ function buildElseIfBlock(expr) {
528
+ const { condition } = expr;
529
+ return `}else if(${build(condition, context)}){`;
530
+ }
531
+ return buildExpr(ast);
532
+ }
533
+
534
+ // src/utils/get-token-type-words.ts
535
+ function getTokenTypeWords(type) {
536
+ switch (type) {
537
+ case 0 /* IDENT */:
538
+ return "identifier";
539
+ case 1 /* KEYWORD */:
540
+ return "keyword";
541
+ case 2 /* NUMBER */:
542
+ return "number";
543
+ case 4 /* OPERATOR */:
544
+ return "operator";
545
+ case 3 /* STRING */:
546
+ return "string";
547
+ }
548
+ }
549
+
550
+ // src/core/generate-ast.ts
551
+ function generateAst(tokens, config) {
552
+ let cursor = 0, generatingNamespace = false;
553
+ function expectOrThrow(type, value) {
554
+ const token = tokens[cursor];
555
+ const lastToken = tokens[tokens.length - 1];
556
+ if (!token) {
557
+ throw {
558
+ message: `Unexpected end of expression. Expected ${value ? `'${value}'` : `${type === 0 /* IDENT */ ? "an" : "a"} ${getTokenTypeWords(type)}`}.`,
559
+ pos: lastToken.pos + lastToken.value.length - 1
560
+ };
561
+ }
562
+ if (token.type !== type) {
563
+ throw {
564
+ message: `Unexpected token type. Expected ${value ? `'${value}'` : getTokenTypeWords(type)} but got ${getTokenTypeWords(token.type)} instead.`,
565
+ pos: token.pos
566
+ };
567
+ }
568
+ if (value !== void 0 && token.value !== value) {
569
+ throw {
570
+ message: `Unexpected token '${token?.value}'. Expected ${type === 0 /* IDENT */ ? "an" : "a"} ${getTokenTypeWords(type)} instead.`,
571
+ pos: token.pos
572
+ };
573
+ }
574
+ return tokens[cursor++];
575
+ }
576
+ function parseForLoop() {
577
+ const pos = tokens[cursor - 1].pos;
578
+ const variable = expectOrThrow(0 /* IDENT */).value;
579
+ let token;
580
+ try {
581
+ token = expectOrThrow(1 /* KEYWORD */, "in");
582
+ } catch {
583
+ token = expectOrThrow(1 /* KEYWORD */, "of");
584
+ }
585
+ const loopType = token.value === "in" ? 1 /* IN */ : 0 /* OF */;
586
+ const iterable = parseTernaryExpr();
587
+ return { type: 13 /* FOR */, loopType, iterable, variable, pos };
588
+ }
589
+ function parseIfExpression() {
590
+ const condition = parseTernaryExpr();
591
+ return { condition, pos: condition.pos, type: 14 /* IF */ };
592
+ }
593
+ function parseElseExpression() {
594
+ const pos = tokens[cursor - 1].pos;
595
+ try {
596
+ expectOrThrow(1 /* KEYWORD */, "if");
597
+ return { ...parseIfExpression(), type: 15 /* ELSE_IF */, pos };
598
+ } catch {
599
+ return { type: 16 /* ELSE */, pos };
600
+ }
601
+ }
602
+ function extractFnArgs() {
603
+ const args = [];
604
+ const emptyArgs = tokens[cursor]?.type === 4 /* OPERATOR */ && tokens[cursor]?.value === ")";
605
+ if (emptyArgs) {
606
+ return args;
607
+ }
608
+ args.push(parseTernaryExpr());
609
+ while (tokens[cursor]?.type === 4 /* OPERATOR */ && tokens[cursor]?.value === "," && tokens[cursor]?.value !== ")") {
610
+ cursor++;
611
+ args.push(parseTernaryExpr());
612
+ }
613
+ return args;
614
+ }
615
+ function parsePrimaryExpr() {
616
+ const token = tokens[cursor++];
617
+ if (token?.type === 2 /* NUMBER */) {
618
+ return { type: 5 /* NUMBER */, value: token.value, pos: token.pos };
619
+ }
620
+ if (token?.type === 3 /* STRING */) {
621
+ return { type: 4 /* STRING */, value: token.value, pos: token.pos };
622
+ }
623
+ if (token?.type === 1 /* KEYWORD */) {
624
+ if (token.value === "for" && cursor === 1) {
625
+ return parseForLoop();
626
+ }
627
+ if (token.value === "true" || token.value === "false") {
628
+ return {
629
+ type: 10 /* BOOLEAN */,
630
+ true: token.value === "true",
631
+ pos: token.pos
632
+ };
633
+ }
634
+ if (token.value === "undefined") {
635
+ return { type: 11 /* UNDEFINED */, pos: token.pos };
636
+ }
637
+ if (token.value === "null") {
638
+ return { type: 12 /* NULL */, pos: token.pos };
639
+ }
640
+ if (token.value === "end" && tokens.length === 1) {
641
+ return { type: 17 /* END */, pos: token.pos };
642
+ }
643
+ if (token.value === "if" && cursor === 1) {
644
+ return parseIfExpression();
645
+ }
646
+ if (token.value === "else" && cursor === 1) {
647
+ return parseElseExpression();
648
+ }
649
+ }
650
+ if (token?.type === 0 /* IDENT */) {
651
+ return { type: 7 /* IDENT */, value: token.value, pos: token.pos };
652
+ }
653
+ if (token?.type === 4 /* OPERATOR */ && token.value === "(") {
654
+ const expr = parseTernaryExpr();
655
+ expectOrThrow(4 /* OPERATOR */, ")");
656
+ return { type: 9 /* GROUP */, expr, pos: token.pos };
657
+ }
658
+ if (token?.type === 4 /* OPERATOR */ && unaryOperators.has(token.value)) {
659
+ const operator = token.value;
660
+ const expr = parseTernaryExpr();
661
+ return { type: 2 /* UNARY */, operator, expr, pos: token.pos };
662
+ }
663
+ if (cursor > tokens.length) {
664
+ throw {
665
+ message: `Unexpected end of expression.`,
666
+ pos: tokens[tokens.length - 1].pos
667
+ };
668
+ }
669
+ throw {
670
+ message: `Unexpected token '${token?.value}'.`,
671
+ pos: token.pos
672
+ };
673
+ }
674
+ function parsePropertyAccess() {
675
+ let left = parsePrimaryExpr();
676
+ while (tokens[cursor]) {
677
+ const token = tokens[cursor];
678
+ if (token?.type === 4 /* OPERATOR */ && token?.value === "(") {
679
+ cursor++;
680
+ if (!generatingNamespace && !config.allowFnCalls) {
681
+ throw {
682
+ message: "Function calls are not allowed.",
683
+ pos: tokens[cursor - 1].pos
684
+ };
685
+ }
686
+ const args = extractFnArgs();
687
+ expectOrThrow(4 /* OPERATOR */, ")");
688
+ left = {
689
+ type: 3 /* CALL */,
690
+ expr: left,
691
+ args,
692
+ pos: tokens[cursor - 1].pos
693
+ };
694
+ } else if (token?.type === 4 /* OPERATOR */ && token?.value === "?." && tokens[cursor + 1]?.type === 4 /* OPERATOR */ && tokens[cursor + 1]?.value === "(") {
695
+ cursor++;
696
+ cursor++;
697
+ if (!generatingNamespace && !config.allowFnCalls) {
698
+ throw {
699
+ message: "Function calls are not allowed.",
700
+ pos: tokens[cursor - 1].pos
701
+ };
702
+ }
703
+ const args = extractFnArgs();
704
+ expectOrThrow(4 /* OPERATOR */, ")");
705
+ left = {
706
+ type: 3 /* CALL */,
707
+ expr: left,
708
+ args,
709
+ optional: true,
710
+ pos: tokens[cursor - 1].pos
711
+ };
712
+ } else if (token?.type === 4 /* OPERATOR */ && propertyAccessOperators.has(token?.value)) {
713
+ const isNamespace = token?.value === "::";
714
+ const isBracketNotation = token?.value === "[";
715
+ const isOptional = token?.value === "?.";
716
+ cursor++;
717
+ if (isNamespace && (tokens[cursor - 2]?.type !== 0 /* IDENT */ || tokens[cursor]?.type !== 0 /* IDENT */)) {
718
+ throw {
719
+ message: `Invalid namespaces access. Expected syntax <IDENTIFIER>::<IDENTIFIER>, but got '${tokens[cursor - 2]?.value}::${tokens[cursor]?.value}' instead.`,
720
+ pos: tokens[cursor]?.pos
721
+ };
722
+ }
723
+ if (isNamespace) {
724
+ generatingNamespace = true;
725
+ const right = parsePrimaryExpr();
726
+ left = {
727
+ type: 6 /* NAMESPACE */,
728
+ left,
729
+ right,
730
+ pos: tokens[cursor - 1].pos
731
+ };
732
+ } else if (isBracketNotation) {
733
+ const right = parseTernaryExpr();
734
+ expectOrThrow(4 /* OPERATOR */, "]");
735
+ left = {
736
+ type: 8 /* PROP_ACCESS */,
737
+ right,
738
+ left,
739
+ bracketNotation: true,
740
+ pos: tokens[cursor - 1].pos
741
+ };
742
+ } else if (isOptional) {
743
+ if (tokens[cursor]?.type === 4 /* OPERATOR */ && tokens[cursor]?.value === "[") {
744
+ cursor++;
745
+ const right = parseTernaryExpr();
746
+ expectOrThrow(4 /* OPERATOR */, "]");
747
+ left = {
748
+ type: 8 /* PROP_ACCESS */,
749
+ left,
750
+ right,
751
+ bracketNotation: true,
752
+ optional: true,
753
+ pos: tokens[cursor - 1].pos
754
+ };
755
+ } else {
756
+ const right = parsePrimaryExpr();
757
+ left = {
758
+ type: 8 /* PROP_ACCESS */,
759
+ left,
760
+ right,
761
+ optional: true,
762
+ pos: tokens[cursor - 1].pos
763
+ };
764
+ }
765
+ } else {
766
+ const right = parsePrimaryExpr();
767
+ left = {
768
+ type: 8 /* PROP_ACCESS */,
769
+ left,
770
+ right,
771
+ pos: tokens[cursor - 1].pos
772
+ };
773
+ }
774
+ } else {
775
+ break;
776
+ }
777
+ }
778
+ generatingNamespace = false;
779
+ return left;
780
+ }
781
+ function parseMultiplicativeExpr() {
782
+ let left = parsePropertyAccess();
783
+ while (tokens[cursor]?.type === 4 /* OPERATOR */ && multiplicativeOperators.has(tokens[cursor]?.value)) {
784
+ const operator = tokens[cursor++].value;
785
+ const right = parsePropertyAccess();
786
+ left = {
787
+ type: 0 /* BINARY */,
788
+ left,
789
+ right,
790
+ operator,
791
+ pos: tokens[cursor - 1].pos
792
+ };
793
+ }
794
+ return left;
795
+ }
796
+ function parseAdditiveExpr() {
797
+ let left = parseMultiplicativeExpr();
798
+ while (tokens[cursor]?.type === 4 /* OPERATOR */ && additiveOperators.has(tokens[cursor]?.value)) {
799
+ const operator = tokens[cursor++].value;
800
+ const right = parseMultiplicativeExpr();
801
+ left = {
802
+ type: 0 /* BINARY */,
803
+ left,
804
+ right,
805
+ operator,
806
+ pos: tokens[cursor - 1].pos
807
+ };
808
+ }
809
+ return left;
810
+ }
811
+ function parseBitwiseExpr() {
812
+ let left = parseAdditiveExpr();
813
+ while (tokens[cursor]?.type === 4 /* OPERATOR */ && comparisonOperators.has(tokens[cursor]?.value)) {
814
+ const operator = tokens[cursor++].value;
815
+ const right = parseAdditiveExpr();
816
+ left = {
817
+ type: 0 /* BINARY */,
818
+ left,
819
+ right,
820
+ operator,
821
+ pos: tokens[cursor - 1].pos
822
+ };
823
+ }
824
+ return left;
825
+ }
826
+ function parseComparisonExpr() {
827
+ let left = parseBitwiseExpr();
828
+ while (tokens[cursor]?.type === 4 /* OPERATOR */ && comparisonOperators.has(tokens[cursor]?.value)) {
829
+ const operator = tokens[cursor++].value;
830
+ const right = parseBitwiseExpr();
831
+ left = {
832
+ type: 0 /* BINARY */,
833
+ left,
834
+ right,
835
+ operator,
836
+ pos: tokens[cursor - 1].pos
837
+ };
838
+ }
839
+ return left;
840
+ }
841
+ function parseEqualityExpr() {
842
+ let left = parseComparisonExpr();
843
+ while (tokens[cursor]?.type === 4 /* OPERATOR */ && equalityOperators.has(tokens[cursor]?.value)) {
844
+ const operator = tokens[cursor++].value;
845
+ const right = parseComparisonExpr();
846
+ left = {
847
+ type: 0 /* BINARY */,
848
+ left,
849
+ right,
850
+ operator,
851
+ pos: tokens[cursor - 1].pos
852
+ };
853
+ }
854
+ return left;
855
+ }
856
+ function parseLogicalOrExpr() {
857
+ let left = parseLogicalAndExpr();
858
+ while (tokens[cursor]?.type === 4 /* OPERATOR */ && tokens[cursor]?.value === "||") {
859
+ const operator = tokens[cursor++].value;
860
+ const right = parseLogicalAndExpr();
861
+ left = {
862
+ type: 0 /* BINARY */,
863
+ left,
864
+ right,
865
+ operator,
866
+ pos: tokens[cursor - 1].pos
867
+ };
868
+ }
869
+ return left;
870
+ }
871
+ function parseLogicalAndExpr() {
872
+ let left = parseNullishCoalesceExpr();
873
+ while (tokens[cursor]?.type === 4 /* OPERATOR */ && tokens[cursor]?.value === "&&") {
874
+ const operator = tokens[cursor++].value;
875
+ const right = parseNullishCoalesceExpr();
876
+ left = {
877
+ type: 0 /* BINARY */,
878
+ left,
879
+ right,
880
+ operator,
881
+ pos: tokens[cursor - 1].pos
882
+ };
883
+ }
884
+ return left;
885
+ }
886
+ function parseNullishCoalesceExpr() {
887
+ let left = parseEqualityExpr();
888
+ while (tokens[cursor]?.type === 4 /* OPERATOR */ && tokens[cursor]?.value === "??") {
889
+ const operator = tokens[cursor++].value;
890
+ const right = parseEqualityExpr();
891
+ left = {
892
+ type: 0 /* BINARY */,
893
+ left,
894
+ right,
895
+ operator,
896
+ pos: tokens[cursor - 1].pos
897
+ };
898
+ }
899
+ return left;
900
+ }
901
+ function parseTernaryExpr() {
902
+ const condition = parseLogicalOrExpr();
903
+ if (tokens[cursor]?.type !== 4 /* OPERATOR */ || tokens[cursor]?.value !== "?") {
904
+ return condition;
905
+ }
906
+ cursor++;
907
+ const left = parseTernaryExpr();
908
+ expectOrThrow(4 /* OPERATOR */, ":");
909
+ const right = parseTernaryExpr();
910
+ return {
911
+ type: 1 /* TERNARY */,
912
+ left,
913
+ right,
914
+ condition,
915
+ pos: tokens[cursor - 1].pos
916
+ };
917
+ }
918
+ const ast = parseTernaryExpr();
919
+ return ast;
920
+ }
921
+
922
+ // src/core/parse.ts
923
+ function parse(templateBlock, { delimiters }) {
924
+ const openingTagWithWhitespaceCtrl = `${delimiters.openingTag}${delimiters.whitespaceTrim}`;
925
+ const closingTagWithWhitespaceCtrl = `${delimiters.whitespaceTrim}${delimiters.closingTag}`;
926
+ const leftTrim = templateBlock.startsWith(openingTagWithWhitespaceCtrl);
927
+ const rightTrim = templateBlock.endsWith(closingTagWithWhitespaceCtrl);
928
+ const isComment = templateBlock.startsWith(
929
+ leftTrim ? openingTagWithWhitespaceCtrl + delimiters.commentTag : delimiters.openingTag + delimiters.commentTag
930
+ );
931
+ if (isComment) {
932
+ return { isComment, leftTrim, rightTrim };
933
+ }
934
+ const inner = templateBlock.slice(
935
+ leftTrim ? openingTagWithWhitespaceCtrl.length : delimiters.openingTag.length,
936
+ templateBlock.length - (rightTrim ? closingTagWithWhitespaceCtrl.length : delimiters.closingTag.length)
937
+ );
938
+ const trimmed = inner.trim();
939
+ const isBlock = trimmed.startsWith("for") || trimmed.startsWith("if") || trimmed.startsWith("else");
940
+ const requiresBlockClose = trimmed.startsWith("for") || trimmed.startsWith("if");
941
+ const isBlockEnd = trimmed === "end";
942
+ const hasContext = trimmed.startsWith("for");
943
+ return {
944
+ leftTrim,
945
+ rightTrim,
946
+ inner,
947
+ isBlock,
948
+ isBlockEnd,
949
+ hasContext,
950
+ requiresBlockClose
951
+ };
952
+ }
953
+
954
+ // src/core/tokenize.ts
955
+ function tokenize(expr) {
956
+ let cursor = 0, char = "";
957
+ const tokens = [];
958
+ function accumulateKeywordOrIdentifier() {
959
+ let buffer = "";
960
+ if (/[a-zA-Z$_]/.test(char)) {
961
+ let j = cursor;
962
+ while (/[a-zA-Z$_0-9]/.test(expr[j]) && j < expr.length) {
963
+ buffer += expr[j];
964
+ j++;
965
+ }
966
+ tokens.push({
967
+ type: keywords.has(buffer) ? 1 /* KEYWORD */ : 0 /* IDENT */,
968
+ value: buffer,
969
+ pos: cursor
970
+ });
971
+ cursor = j;
972
+ char = expr[cursor];
973
+ }
974
+ }
975
+ function accumulateStr() {
976
+ let buffer = "";
977
+ if (char === '"' || char === "'" || char === "`") {
978
+ let j = cursor + 1;
979
+ while (expr[j] !== char && j < expr.length) {
980
+ buffer += expr[j];
981
+ j++;
982
+ }
983
+ if (j > expr.length) {
984
+ throw { pos: cursor, message: `Found string without closing quote.` };
985
+ }
986
+ tokens.push({ type: 3 /* STRING */, value: buffer, pos: cursor });
987
+ cursor = j;
988
+ }
989
+ }
990
+ function accumulateNumber() {
991
+ if (/[0-9]/.test(char)) {
992
+ let j = cursor, buffer = "";
993
+ while (/[0-9.oxe]/.test(expr[j]) && j < expr.length) {
994
+ buffer += expr[j];
995
+ j++;
996
+ }
997
+ const numVal = Number(buffer);
998
+ const isNan = Number.isNaN(numVal);
999
+ if (isNan) {
1000
+ throw { pos: cursor, message: "Found invalid number literal." };
1001
+ }
1002
+ tokens.push({ type: 2 /* NUMBER */, value: `${numVal}`, pos: cursor });
1003
+ cursor = j - 1;
1004
+ char = expr[cursor];
1005
+ }
1006
+ }
1007
+ function accumulateOperator() {
1008
+ const op = `${char}${expr[cursor + 1]}`;
1009
+ if (operators.has(op)) {
1010
+ tokens.push({ type: 4 /* OPERATOR */, value: op, pos: cursor });
1011
+ cursor++;
1012
+ return;
1013
+ }
1014
+ if (operators.has(char)) {
1015
+ tokens.push({ type: 4 /* OPERATOR */, value: char, pos: cursor });
1016
+ return;
1017
+ }
1018
+ }
1019
+ while (cursor < expr.length) {
1020
+ char = expr[cursor];
1021
+ accumulateNumber();
1022
+ accumulateKeywordOrIdentifier();
1023
+ accumulateStr();
1024
+ accumulateOperator();
1025
+ if (!/[a-zA-Z$_0-9\s\t\r\n'"`]/.test(char) && !operators.has(char) && !operators.has(expr[cursor - 1] + char)) {
1026
+ throw {
1027
+ message: `Unexpected token '${char}' in expression.`,
1028
+ pos: cursor
1029
+ };
1030
+ }
1031
+ cursor++;
1032
+ }
1033
+ return tokens;
1034
+ }
1035
+
1036
+ // src/core/compile.ts
1037
+ function compile(src, config, meta) {
1038
+ const scope = [];
1039
+ const blockOpeningStack = [];
1040
+ const {
1041
+ delimiters,
1042
+ keepOpeningTagEscapeDelimiter,
1043
+ allowFnCalls,
1044
+ allowedProps,
1045
+ forbiddenProps,
1046
+ autoEscape
1047
+ } = config;
1048
+ let trimNext = false, cursor = 0, body = `let acc="";`;
1049
+ while (cursor < src.length) {
1050
+ let isEscaped2 = function() {
1051
+ let j = templateOpenTagIdx, count = 0;
1052
+ while (j >= delimiters.openingTagEscape.length && src.slice(j - delimiters.openingTagEscape.length, j) === delimiters.openingTagEscape) {
1053
+ count++;
1054
+ j -= delimiters.openingTagEscape.length;
1055
+ }
1056
+ return count % 2 === 1;
1057
+ };
1058
+ var isEscaped = isEscaped2;
1059
+ const templateOpenTagIdx = src.indexOf(delimiters.openingTag, cursor);
1060
+ if (templateOpenTagIdx === -1) {
1061
+ let lastChunk = src.slice(cursor);
1062
+ if (trimNext) lastChunk = lastChunk.trimStart();
1063
+ if (lastChunk) body += `acc+=\`${escapeRawText(lastChunk)}\`;`;
1064
+ break;
1065
+ }
1066
+ if (isEscaped2()) {
1067
+ let escapedChunk = src.slice(
1068
+ cursor,
1069
+ keepOpeningTagEscapeDelimiter ? templateOpenTagIdx + delimiters.openingTagEscape.length + 1 : templateOpenTagIdx - delimiters.openingTag.length + 1
1070
+ );
1071
+ if (trimNext) {
1072
+ escapedChunk = escapedChunk.trimStart();
1073
+ trimNext = false;
1074
+ }
1075
+ body += `acc+=\`${escapeRawText(escapedChunk)}\`;`;
1076
+ if (!keepOpeningTagEscapeDelimiter)
1077
+ body += `acc+=\`${delimiters.openingTag}\`;`;
1078
+ cursor = templateOpenTagIdx + delimiters.openingTag.length;
1079
+ continue;
1080
+ }
1081
+ const templateEndTagIdx = src.indexOf(
1082
+ delimiters.closingTag,
1083
+ templateOpenTagIdx
1084
+ );
1085
+ if (templateEndTagIdx === -1) {
1086
+ const { line, lineIndex } = getLineAndColumnNumbers(
1087
+ src,
1088
+ templateOpenTagIdx
1089
+ );
1090
+ const { line: lineText, pos } = getLineSnapshot(
1091
+ src,
1092
+ lineIndex,
1093
+ templateOpenTagIdx
1094
+ );
1095
+ throw new MutorCompilerError(
1096
+ "No closing tag found.",
1097
+ line,
1098
+ lineText,
1099
+ pos,
1100
+ meta.path
1101
+ );
1102
+ }
1103
+ const template = src.slice(
1104
+ templateOpenTagIdx,
1105
+ templateEndTagIdx + delimiters.closingTag.length
1106
+ );
1107
+ const {
1108
+ inner,
1109
+ leftTrim,
1110
+ rightTrim,
1111
+ isBlock,
1112
+ isBlockEnd,
1113
+ hasContext,
1114
+ requiresBlockClose,
1115
+ isComment
1116
+ } = parse(template, { delimiters });
1117
+ let rawText = src.slice(cursor, templateOpenTagIdx);
1118
+ if (rawText) {
1119
+ if (trimNext) {
1120
+ rawText = rawText.trimStart();
1121
+ }
1122
+ if (leftTrim) {
1123
+ rawText = rawText.trimEnd();
1124
+ }
1125
+ if (rawText) {
1126
+ body += `acc+=\`${escapeRawText(rawText)}\`;`;
1127
+ }
1128
+ }
1129
+ trimNext = false;
1130
+ cursor = templateEndTagIdx + delimiters.closingTag.length;
1131
+ try {
1132
+ if (!isComment) {
1133
+ const tokens = tokenize(inner);
1134
+ const ast = generateAst(tokens, { allowFnCalls });
1135
+ if (isBlock && requiresBlockClose && hasContext) {
1136
+ scope.push(ast.variable);
1137
+ blockOpeningStack.push({
1138
+ type: 0 /* LOOP */,
1139
+ pos: templateOpenTagIdx
1140
+ });
1141
+ } else if (isBlock && requiresBlockClose && !hasContext) {
1142
+ blockOpeningStack.push({
1143
+ type: 1 /* NON_LOOP */,
1144
+ pos: templateOpenTagIdx
1145
+ });
1146
+ }
1147
+ if (isBlockEnd) {
1148
+ const lastBlockOpened = blockOpeningStack.pop();
1149
+ if (lastBlockOpened?.type === 0 /* LOOP */) scope.pop();
1150
+ if (lastBlockOpened === void 0)
1151
+ throw {
1152
+ message: "Unexpected end of block",
1153
+ pos: templateOpenTagIdx
1154
+ };
1155
+ }
1156
+ const js = build(ast, { allowedProps, forbiddenProps, scope });
1157
+ if (isBlock || isBlockEnd) {
1158
+ body += js;
1159
+ } else {
1160
+ body += autoEscape && !js.startsWith("namespaces.Mutor.include") ? `acc+=escapeFn(${js});` : `acc+=${js};`;
1161
+ }
1162
+ }
1163
+ if (rightTrim) trimNext = true;
1164
+ } catch (e) {
1165
+ const { message, pos: relPos } = e;
1166
+ const { line, lineIndex } = getLineAndColumnNumbers(
1167
+ src,
1168
+ templateOpenTagIdx
1169
+ );
1170
+ const { line: lineText, pos } = getLineSnapshot(
1171
+ src,
1172
+ lineIndex,
1173
+ templateOpenTagIdx
1174
+ );
1175
+ throw new MutorCompilerError(
1176
+ message,
1177
+ line,
1178
+ lineText,
1179
+ pos + relPos + (leftTrim ? delimiters.openingTag.length + delimiters.whitespaceTrim.length : delimiters.openingTag.length),
1180
+ meta.path
1181
+ );
1182
+ }
1183
+ }
1184
+ if (blockOpeningStack.length) {
1185
+ const lastPos = blockOpeningStack.pop()?.pos;
1186
+ const { line, lineIndex } = getLineAndColumnNumbers(src, lastPos);
1187
+ const { line: lineText, pos } = getLineSnapshot(src, lineIndex, lastPos);
1188
+ throw new MutorCompilerError(
1189
+ "Unclosed block detected.",
1190
+ line,
1191
+ lineText,
1192
+ pos + delimiters.openingTag.length,
1193
+ meta.path
1194
+ );
1195
+ }
1196
+ body += `return acc;`;
1197
+ return new Function(
1198
+ "ctx",
1199
+ "namespaces",
1200
+ "allowedProps",
1201
+ "forbiddenProps",
1202
+ "escapeFn",
1203
+ "validateComputedProps",
1204
+ body
1205
+ );
1206
+ }
1207
+
1208
+ // src/core/mutor.ts
1209
+ var Mutor = class {
1210
+ constructor(config = {}) {
1211
+ this.__currentRenderedPath = "";
1212
+ this.__includeStack = /* @__PURE__ */ new Set();
1213
+ this.__cacheSize = 0;
1214
+ this.__config = { ...defaultConfig };
1215
+ this.__compiledTemplatesMap = /* @__PURE__ */ new Map();
1216
+ this.__currentContext = null;
1217
+ this.__namespaces = {
1218
+ ...namespaces,
1219
+ Mutor: {
1220
+ include: (path, ctx) => {
1221
+ if (this.__includeStack.has(path)) {
1222
+ throw new MutorError(
1223
+ `Circular include detected:
1224
+ ${Array.from(this.__includeStack).join("\n")}
1225
+ ${path}`
1226
+ );
1227
+ }
1228
+ try {
1229
+ this.__includeStack.add(path);
1230
+ return this.renderComponent(path, ctx ?? this.__currentContext);
1231
+ } finally {
1232
+ this.__includeStack.delete(path);
1233
+ }
1234
+ }
1235
+ }
1236
+ };
1237
+ this.addConfig(config);
1238
+ Object.defineProperty(this.__namespaces.Mutor, "$$context", {
1239
+ get: () => {
1240
+ return this.__currentContext;
1241
+ }
1242
+ });
1243
+ }
1244
+ addConfig(conf) {
1245
+ const {
1246
+ autoEscape,
1247
+ delimiters: overrideDelimeters,
1248
+ allowedProps,
1249
+ forbiddenProps,
1250
+ keepOpeningTagEscapeDelimiter,
1251
+ allowFnCalls,
1252
+ cache,
1253
+ build: build2
1254
+ } = conf;
1255
+ this.__config = {
1256
+ build: {
1257
+ include: /* @__PURE__ */ new Set([...build2?.include || defaultConfig.build.include]),
1258
+ exclude: /* @__PURE__ */ new Set([
1259
+ ...defaultConfig.build.exclude,
1260
+ ...build2?.exclude || []
1261
+ ])
1262
+ },
1263
+ autoEscape: autoEscape === true ? true : autoEscape !== false,
1264
+ allowedProps: allowedProps || defaultConfig.allowedProps,
1265
+ allowFnCalls: !!allowFnCalls,
1266
+ cache: { ...defaultConfig.cache, ...cache || {} },
1267
+ forbiddenProps: /* @__PURE__ */ new Set([
1268
+ ...defaultConfig.forbiddenProps,
1269
+ ...forbiddenProps || []
1270
+ ]),
1271
+ keepOpeningTagEscapeDelimiter: keepOpeningTagEscapeDelimiter === true ? true : keepOpeningTagEscapeDelimiter !== false,
1272
+ delimiters: {
1273
+ ...defaultConfig.delimiters,
1274
+ ...overrideDelimeters || {}
1275
+ }
1276
+ };
1277
+ return this.__config;
1278
+ }
1279
+ restoreDefaultConfig() {
1280
+ this.__config = { ...defaultConfig };
1281
+ }
1282
+ compile(template) {
1283
+ return compile(template, this.__config, {
1284
+ path: this.__currentRenderedPath || "anonymous"
1285
+ });
1286
+ }
1287
+ render(template, context) {
1288
+ const prevContext = this.__currentContext;
1289
+ if (prevContext !== context) {
1290
+ this.__currentContext = context;
1291
+ }
1292
+ const result = this.compile(template)(
1293
+ validateContext(context),
1294
+ this.__namespaces,
1295
+ this.__config.allowedProps,
1296
+ this.__config.forbiddenProps,
1297
+ escapeFn,
1298
+ validateComputedProp
1299
+ );
1300
+ this.__currentContext = prevContext;
1301
+ return result;
1302
+ }
1303
+ renderComponent(identifier, context) {
1304
+ if (!this.__compiledTemplatesMap.has(identifier)) {
1305
+ throw new MutorError(
1306
+ `No template exists with the identifier '${identifier}'`
1307
+ );
1308
+ }
1309
+ const prevRenderComponentIdentifier = this.__currentRenderedPath;
1310
+ const prevContext = this.__currentContext;
1311
+ const compiled = this.__compiledTemplatesMap.get(identifier);
1312
+ this.__currentContext = context;
1313
+ this.__currentRenderedPath = identifier;
1314
+ const result = compiled.fn(
1315
+ validateContext(context),
1316
+ this.__namespaces,
1317
+ this.__config.allowedProps,
1318
+ this.__config.forbiddenProps,
1319
+ escapeFn,
1320
+ validateComputedProp
1321
+ );
1322
+ this.__currentContext = prevContext;
1323
+ this.__currentRenderedPath = prevRenderComponentIdentifier;
1324
+ return result;
1325
+ }
1326
+ registerComponent(identifier, template) {
1327
+ const templateSize = template.length * 2 + 500;
1328
+ if (this.__cacheSize + templateSize > this.__config.cache.maxSize) {
1329
+ if (!this.createEntrySpaceForTemplate(templateSize)) {
1330
+ throw new MutorError(
1331
+ `The template for the component '${identifier}' is too large. Consider increasing 'cache.maxSize' in the config`
1332
+ );
1333
+ }
1334
+ }
1335
+ this.__cacheSize += template.length * 2 + 500;
1336
+ this.__compiledTemplatesMap.set(identifier, {
1337
+ fn: this.compile(template),
1338
+ size: templateSize
1339
+ });
1340
+ }
1341
+ reset() {
1342
+ this.__config = { ...defaultConfig };
1343
+ this.__compiledTemplatesMap.clear();
1344
+ this.__currentContext = null;
1345
+ this.__cacheSize = 0;
1346
+ }
1347
+ createEntrySpaceForTemplate(targetSize) {
1348
+ if (this.__cacheSize + targetSize < this.__config.cache.maxSize) {
1349
+ return true;
1350
+ }
1351
+ if (targetSize > this.__config.cache.maxSize) {
1352
+ return false;
1353
+ }
1354
+ const firstEntry = this.__compiledTemplatesMap.entries().next().value;
1355
+ if (firstEntry) {
1356
+ const [oldestKey, oldestData] = firstEntry;
1357
+ this.__compiledTemplatesMap.delete(oldestKey);
1358
+ this.__cacheSize -= oldestData.size;
1359
+ }
1360
+ return this.createEntrySpaceForTemplate(targetSize);
1361
+ }
1362
+ getDiagnostics() {
1363
+ const maxSize = this.__config.cache.maxSize;
1364
+ return {
1365
+ bytesUsed: this.__cacheSize,
1366
+ bytesMax: maxSize,
1367
+ readableUsed: `${(this.__cacheSize / 1024 / 1024).toFixed(2)} MB`,
1368
+ readableMax: `${(maxSize / 1024 / 1024).toFixed(2)} MB`,
1369
+ totalEntries: this.__compiledTemplatesMap.size,
1370
+ percentFull: maxSize > 0 ? Math.min(100, Math.round(this.__cacheSize / maxSize * 100)) : 0,
1371
+ avgTemplateSize: this.__compiledTemplatesMap.size > 0 ? Math.round(this.__cacheSize / this.__compiledTemplatesMap.size) : 0
1372
+ };
1373
+ }
1374
+ };
1375
+
1376
+ // src/core/mutor.server.ts
1377
+ var Mutor2 = class extends Mutor {
1378
+ constructor(config = {}) {
1379
+ super(config);
1380
+ this.__namespaces.Mutor.include = (path, context) => {
1381
+ const resolvedPath = toAbsolutePath(this.__currentRenderedPath, path);
1382
+ if (this.__includeStack.has(resolvedPath)) {
1383
+ throw new MutorError(
1384
+ `Circular include detected:
1385
+ ${Array.from(this.__includeStack).join("\n")}
1386
+ ${resolvedPath}`
1387
+ );
1388
+ }
1389
+ try {
1390
+ this.__includeStack.add(resolvedPath);
1391
+ return this.renderFile(resolvedPath, context ?? this.__currentContext);
1392
+ } finally {
1393
+ this.__includeStack.delete(resolvedPath);
1394
+ }
1395
+ };
1396
+ }
1397
+ renderFile(path, context) {
1398
+ const absolutePath = toAbsolutePath(path);
1399
+ let compiled;
1400
+ const prevContext = this.__currentContext;
1401
+ const prevRenderComponentIdentifier = this.__currentRenderedPath;
1402
+ this.__currentContext = context;
1403
+ this.__currentRenderedPath = path;
1404
+ if (this.__config.cache.active && this.__compiledTemplatesMap.has(absolutePath)) {
1405
+ compiled = this.__compiledTemplatesMap.get(absolutePath).fn;
1406
+ } else {
1407
+ const template = (0, import_node_fs.readFileSync)(absolutePath, "utf-8");
1408
+ compiled = this.compile(template);
1409
+ if (this.__config.cache.active) {
1410
+ const templateSize = template.length * 2 + 500;
1411
+ if (this.__cacheSize + templateSize > this.__config.cache.maxSize) {
1412
+ if (this.createEntrySpaceForTemplate(templateSize)) {
1413
+ this.__compiledTemplatesMap.set(absolutePath, {
1414
+ fn: compiled,
1415
+ size: templateSize
1416
+ });
1417
+ this.__cacheSize += templateSize;
1418
+ }
1419
+ } else {
1420
+ this.__compiledTemplatesMap.set(absolutePath, {
1421
+ fn: compiled,
1422
+ size: templateSize
1423
+ });
1424
+ this.__cacheSize += templateSize;
1425
+ }
1426
+ }
1427
+ }
1428
+ const result = compiled(
1429
+ validateContext(context),
1430
+ this.__namespaces,
1431
+ this.__config.allowedProps,
1432
+ this.__config.forbiddenProps,
1433
+ escapeFn,
1434
+ validateComputedProp
1435
+ );
1436
+ this.__currentContext = prevContext;
1437
+ this.__currentRenderedPath = prevRenderComponentIdentifier;
1438
+ return result;
1439
+ }
1440
+ async buildDir(src, destination, context) {
1441
+ const absoluteDestinationPath = toAbsolutePath(destination);
1442
+ const absoluteSrcPath = toAbsolutePath(src);
1443
+ await (0, import_promises.mkdir)(absoluteDestinationPath, { recursive: true });
1444
+ const entries = await (0, import_promises.readdir)(absoluteSrcPath, { withFileTypes: true });
1445
+ await Promise.all(
1446
+ entries.map(async (entry) => {
1447
+ const srcPath = (0, import_node_path2.join)(absoluteSrcPath, entry.name);
1448
+ const destinationPath = (0, import_node_path2.join)(absoluteDestinationPath, entry.name);
1449
+ if (this.__config.build.exclude.has(entry.name)) {
1450
+ return;
1451
+ }
1452
+ if (entry.isDirectory()) {
1453
+ return this.buildDir(srcPath, destinationPath, context);
1454
+ }
1455
+ const extension = (0, import_node_path2.extname)(srcPath);
1456
+ if (this.__config.build.include.has(extension)) {
1457
+ const rendered = this.renderFile(srcPath, context);
1458
+ await (0, import_promises.writeFile)(destinationPath, rendered, "utf-8");
1459
+ } else {
1460
+ return await (0, import_promises.copyFile)(srcPath, destinationPath);
1461
+ }
1462
+ })
1463
+ );
1464
+ }
1465
+ async compileDir(src) {
1466
+ const absolutePath = toAbsolutePath(src);
1467
+ const entries = await (0, import_promises.readdir)(absolutePath, { withFileTypes: true });
1468
+ await Promise.all(
1469
+ entries.map(async (entry) => {
1470
+ const absoluteSrcPath = (0, import_node_path2.join)(absolutePath, entry.name);
1471
+ if (this.__config.build.exclude.has(entry.name)) {
1472
+ return;
1473
+ }
1474
+ if (entry.isDirectory()) {
1475
+ return this.compileDir(absoluteSrcPath);
1476
+ }
1477
+ const extension = (0, import_node_path2.extname)(absoluteSrcPath);
1478
+ if (this.__config.build.include.has(extension)) {
1479
+ try {
1480
+ const template = await (0, import_promises.readFile)(absoluteSrcPath, "utf-8");
1481
+ this.registerComponent(absoluteSrcPath, template);
1482
+ } catch {
1483
+ }
1484
+ }
1485
+ })
1486
+ );
1487
+ }
1488
+ };
1489
+
1490
+ // src/bin/cli-errors.ts
1491
+ var ExitCodes = {
1492
+ Success: 0,
1493
+ RuntimeError: 1,
1494
+ ArgumentError: 2
1495
+ };
1496
+ var CliError = class extends Error {
1497
+ constructor(message, exitCode = ExitCodes.RuntimeError) {
1498
+ super(message);
1499
+ this.exitCode = exitCode;
1500
+ this.name = "CliError";
1501
+ }
1502
+ };
1503
+ var ArgumentError = class extends CliError {
1504
+ constructor(message) {
1505
+ super(message, ExitCodes.ArgumentError);
1506
+ this.name = "ArgumentError";
1507
+ }
1508
+ };
1509
+ var FileReadError = class extends CliError {
1510
+ constructor(filePath, cause) {
1511
+ const reason = cause instanceof Error ? cause.message : String(cause);
1512
+ super(
1513
+ `could not read file '${filePath}': ${reason}`,
1514
+ ExitCodes.RuntimeError
1515
+ );
1516
+ this.name = "FileReadError";
1517
+ }
1518
+ };
1519
+ var FileWriteError = class extends CliError {
1520
+ constructor(filePath, cause) {
1521
+ const reason = cause instanceof Error ? cause.message : String(cause);
1522
+ super(
1523
+ `could not write file '${filePath}': ${reason}`,
1524
+ ExitCodes.RuntimeError
1525
+ );
1526
+ this.name = "FileWriteError";
1527
+ }
1528
+ };
1529
+ var JsonParseError = class extends CliError {
1530
+ constructor(filePath, cause) {
1531
+ const reason = cause instanceof Error ? cause.message : String(cause);
1532
+ super(
1533
+ `failed to parse JSON in '${filePath}': ${reason}`,
1534
+ ExitCodes.RuntimeError
1535
+ );
1536
+ this.name = "JsonParseError";
1537
+ }
1538
+ };
1539
+
1540
+ // src/bin/cli.ts
1541
+ var COMMANDS = /* @__PURE__ */ new Set(["compile", "build", "render"]);
1542
+ var OPTIONS = /* @__PURE__ */ new Set(["--out", "--data", "--config"]);
1543
+ var VERSION = `Mutor.js v${version}`;
1544
+ var USAGE = `
1545
+ Usage: mutor <command> <input> [options]
1546
+
1547
+ Commands:
1548
+ compile <template> Compile a template file to its intermediate form
1549
+ build <dir> Render all templates in a directory using a data source
1550
+ render <template> Compile and immediately render a template
1551
+
1552
+ Options:
1553
+ --out <path> Output file or directory (defaults to stdout for compile/render)
1554
+ --data <path> JSON data file to use as render context (required for build/render)
1555
+ --config <path> JSON config file to pass to Mutor
1556
+ --version Print the version and exit
1557
+ --help Show this help message
1558
+
1559
+ Exit codes:
1560
+ 0 success
1561
+ 1 runtime error (I/O failure, render failure, etc.)
1562
+ 2 argument error (bad flags, missing or wrong-typed values)
1563
+ `.trim();
1564
+ function safeReadFile(filePath) {
1565
+ try {
1566
+ return (0, import_node_fs2.readFileSync)(filePath, "utf-8");
1567
+ } catch (err) {
1568
+ throw new FileReadError(filePath, err);
1569
+ }
1570
+ }
1571
+ function safeWriteFile(filePath, content) {
1572
+ try {
1573
+ (0, import_node_fs2.writeFileSync)(filePath, content, "utf-8");
1574
+ } catch (err) {
1575
+ throw new FileWriteError(filePath, err);
1576
+ }
1577
+ }
1578
+ function safeParseJsonFile(filePath) {
1579
+ const raw = safeReadFile(filePath);
1580
+ try {
1581
+ return JSON.parse(raw);
1582
+ } catch (err) {
1583
+ throw new JsonParseError(filePath, err);
1584
+ }
1585
+ }
1586
+ function assertIsFile(filePath, flag) {
1587
+ let stat;
1588
+ try {
1589
+ stat = (0, import_node_fs2.statSync)(filePath);
1590
+ } catch {
1591
+ throw new ArgumentError(`${flag} path '${filePath}' does not exist`);
1592
+ }
1593
+ if (!stat.isFile()) {
1594
+ throw new ArgumentError(`${flag} path '${filePath}' is not a file`);
1595
+ }
1596
+ }
1597
+ function assertIsDirectory(dirPath, flag) {
1598
+ let stat;
1599
+ try {
1600
+ stat = (0, import_node_fs2.statSync)(dirPath);
1601
+ } catch {
1602
+ throw new ArgumentError(`${flag} path '${dirPath}' does not exist`);
1603
+ }
1604
+ if (!stat.isDirectory()) {
1605
+ throw new ArgumentError(`${flag} path '${dirPath}' is not a directory`);
1606
+ }
1607
+ }
1608
+ function assertExtension(filePath, flag, ...extensions) {
1609
+ const matches = extensions.some((ext) => filePath.endsWith(ext));
1610
+ if (!matches) {
1611
+ throw new ArgumentError(
1612
+ `${flag} expects a file with extension ${extensions.join(" or ")} \u2014 got '${filePath}'`
1613
+ );
1614
+ }
1615
+ }
1616
+ function parseArgs(rawArgs) {
1617
+ if (rawArgs.length === 0 || rawArgs[0] === "--help") {
1618
+ console.log(USAGE);
1619
+ (0, import_node_process.exit)(ExitCodes.Success);
1620
+ }
1621
+ if (rawArgs[0] === "--version") {
1622
+ console.log(VERSION);
1623
+ (0, import_node_process.exit)(ExitCodes.Success);
1624
+ }
1625
+ const command = rawArgs[0];
1626
+ if (!COMMANDS.has(command)) {
1627
+ throw new ArgumentError(`unknown command '${command}'
1628
+
1629
+ ${USAGE}`);
1630
+ }
1631
+ const commandData = rawArgs[1];
1632
+ if (!commandData || commandData.startsWith("--")) {
1633
+ throw new ArgumentError(
1634
+ `command '${command}' requires an input path as its first argument
1635
+
1636
+ ${USAGE}`
1637
+ );
1638
+ }
1639
+ const struct = { command, commandData };
1640
+ for (let i = 2; i < rawArgs.length; i++) {
1641
+ const flag = rawArgs[i];
1642
+ if (!OPTIONS.has(flag)) {
1643
+ throw new ArgumentError(`unknown option '${flag}'
1644
+
1645
+ ${USAGE}`);
1646
+ }
1647
+ const value = rawArgs[i + 1];
1648
+ if (!value || value.startsWith("--")) {
1649
+ throw new ArgumentError(`option '${flag}' requires a value`);
1650
+ }
1651
+ struct[flag] = value;
1652
+ i++;
1653
+ }
1654
+ return struct;
1655
+ }
1656
+ function handleCompileCommand(mutor, args) {
1657
+ const inputPath = toAbsolutePath(args.commandData);
1658
+ assertIsFile(inputPath, "<input>");
1659
+ const template = safeReadFile(inputPath);
1660
+ const compiled = mutor.compile(template);
1661
+ const output = compiled.toString();
1662
+ if (args["--out"]) {
1663
+ const outPath = toAbsolutePath(args["--out"]);
1664
+ registerCleanup(() => {
1665
+ try {
1666
+ (0, import_node_fs2.rmSync)(outPath, { force: true });
1667
+ } catch {
1668
+ }
1669
+ });
1670
+ safeWriteFile(outPath, output);
1671
+ console.log(`Compiled \u2192 ${args["--out"]}`);
1672
+ } else {
1673
+ console.log(output);
1674
+ }
1675
+ }
1676
+ async function handleBuildCommand(mutor, args) {
1677
+ if (!args["--data"]) {
1678
+ throw new ArgumentError("'build' requires a data source via --data");
1679
+ }
1680
+ if (!args["--out"]) {
1681
+ throw new ArgumentError("'build' requires an output directory via --out");
1682
+ }
1683
+ const inputPath = toAbsolutePath(args.commandData);
1684
+ const dataPath = toAbsolutePath(args["--data"]);
1685
+ const outPath = toAbsolutePath(args["--out"]);
1686
+ assertIsDirectory(inputPath, "<input>");
1687
+ assertIsFile(dataPath, "--data");
1688
+ assertExtension(dataPath, "--data", ".json");
1689
+ const context = safeParseJsonFile(dataPath);
1690
+ let buildStarted = false;
1691
+ registerCleanup(() => {
1692
+ if (buildStarted) {
1693
+ console.warn("\nBuild interrupted \u2014 removing partial output...");
1694
+ try {
1695
+ (0, import_node_fs2.rmSync)(outPath, { recursive: true, force: true });
1696
+ } catch {
1697
+ }
1698
+ }
1699
+ });
1700
+ buildStarted = true;
1701
+ await mutor.buildDir(inputPath, outPath, context);
1702
+ buildStarted = false;
1703
+ console.log(`Built \u2192 ${args["--out"]}`);
1704
+ }
1705
+ async function handleRenderCommand(mutor, args) {
1706
+ if (!args["--data"]) {
1707
+ throw new ArgumentError("'render' requires a data source via --data");
1708
+ }
1709
+ const inputPath = toAbsolutePath(args.commandData);
1710
+ const dataPath = toAbsolutePath(args["--data"]);
1711
+ assertIsFile(inputPath, "<input>");
1712
+ assertIsFile(dataPath, "--data");
1713
+ assertExtension(dataPath, "--data", ".json");
1714
+ const context = safeParseJsonFile(dataPath);
1715
+ const template = safeReadFile(inputPath);
1716
+ const output = mutor.render(template, context);
1717
+ if (args["--out"]) {
1718
+ const outPath = toAbsolutePath(args["--out"]);
1719
+ registerCleanup(() => {
1720
+ try {
1721
+ (0, import_node_fs2.rmSync)(outPath, { force: true });
1722
+ } catch {
1723
+ }
1724
+ });
1725
+ safeWriteFile(outPath, output);
1726
+ console.log(`Rendered \u2192 ${args["--out"]}`);
1727
+ } else {
1728
+ console.log(output);
1729
+ }
1730
+ }
1731
+ var cleanupFn = null;
1732
+ function registerCleanup(fn) {
1733
+ cleanupFn = fn;
1734
+ }
1735
+ function handleSignal(signal) {
1736
+ console.warn(`
1737
+ Received ${signal}.`);
1738
+ cleanupFn?.();
1739
+ (0, import_node_process.exit)(ExitCodes.RuntimeError);
1740
+ }
1741
+ process.on("SIGINT", () => handleSignal("SIGINT"));
1742
+ process.on("SIGTERM", () => handleSignal("SIGTERM"));
1743
+ async function main() {
1744
+ const args = parseArgs(import_node_process.argv.slice(2));
1745
+ const mutor = new Mutor2();
1746
+ if (args["--config"]) {
1747
+ const configPath = toAbsolutePath(args["--config"]);
1748
+ assertIsFile(configPath, "--config");
1749
+ assertExtension(configPath, "--config", ".json");
1750
+ const config = safeParseJsonFile(configPath);
1751
+ mutor.addConfig(config);
1752
+ }
1753
+ switch (args.command) {
1754
+ case "compile":
1755
+ handleCompileCommand(mutor, args);
1756
+ break;
1757
+ case "build":
1758
+ await handleBuildCommand(mutor, args);
1759
+ break;
1760
+ case "render":
1761
+ await handleRenderCommand(mutor, args);
1762
+ break;
1763
+ }
1764
+ }
1765
+ main().catch((err) => {
1766
+ if (err instanceof CliError) {
1767
+ console.error(`error: ${err.message}`);
1768
+ (0, import_node_process.exit)(err.exitCode);
1769
+ }
1770
+ console.error("unexpected error:", err);
1771
+ (0, import_node_process.exit)(ExitCodes.RuntimeError);
1772
+ });
1773
+ // Annotate the CommonJS export names for ESM import in node:
1774
+ 0 && (module.exports = {
1775
+ handleBuildCommand,
1776
+ handleCompileCommand,
1777
+ handleRenderCommand,
1778
+ parseArgs,
1779
+ safeParseJsonFile,
1780
+ safeReadFile,
1781
+ safeWriteFile
1782
+ });
1783
+ //# sourceMappingURL=cli.cjs.map