@jaredwray/fumanchu 4.6.1 → 4.7.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.
@@ -2,23 +2,97 @@ import { CacheableMemory } from "@cacheable/memory";
2
2
  import HandlebarsLib from "handlebars";
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
- import tag from "html-tag";
6
- import codeBlock from "to-gfm-code-block";
7
5
  import { parseDate } from "chrono-node";
8
6
  import dayjs from "dayjs";
9
7
  import isBetween from "dayjs/plugin/isBetween.js";
10
8
  import relativeTime from "dayjs/plugin/relativeTime.js";
11
9
  import timezone from "dayjs/plugin/timezone.js";
12
10
  import utc from "dayjs/plugin/utc.js";
13
- import isGlob from "is-glob";
14
11
  import micromatch from "micromatch";
15
- import striptags from "striptags";
16
12
  import process$1 from "node:process";
17
13
  import ent from "ent";
18
- import { Remarkable } from "remarkable";
19
- import kindOf from "kind-of";
14
+ import MarkdownIt from "markdown-it";
20
15
  import { escape } from "node:querystring";
21
16
  import { format, parse, resolve } from "node:url";
17
+ //#region src/helper-registry-base.ts
18
+ /**
19
+ * Base class for helper registries. Holds all state-management logic and
20
+ * leaves `init()` empty so environment-specific subclasses decide which
21
+ * helper sets to register. This lets the browser registry avoid importing
22
+ * any Node-only helper files.
23
+ */
24
+ var HelperRegistryBase = class {
25
+ _helpers = [];
26
+ constructor() {
27
+ this.init();
28
+ }
29
+ /**
30
+ * Get all registered helpers.
31
+ */
32
+ get helpers() {
33
+ return this._helpers;
34
+ }
35
+ /**
36
+ * Initialize the helper registry. Overridden by subclasses.
37
+ */
38
+ init() {}
39
+ /**
40
+ * Register a helper. Returns true if the helper was added, false if a
41
+ * helper with the same name was already present.
42
+ */
43
+ register(helper) {
44
+ if (this.has(helper.name)) return false;
45
+ this._helpers.push(helper);
46
+ return true;
47
+ }
48
+ /**
49
+ * Register multiple helpers.
50
+ */
51
+ registerHelpers(helpers) {
52
+ for (const helper of helpers) this.register(helper);
53
+ }
54
+ /**
55
+ * Check if a helper is registered.
56
+ */
57
+ has(name) {
58
+ return this._helpers.some((helper) => helper.name === name);
59
+ }
60
+ /**
61
+ * Filter helpers by the given criteria.
62
+ */
63
+ filter(filter) {
64
+ let result = this._helpers;
65
+ if (filter.names) {
66
+ const names = filter.names;
67
+ result = result.filter((helper) => names.includes(helper.name));
68
+ }
69
+ if (filter.categories) {
70
+ const categories = filter.categories;
71
+ result = result.filter((helper) => categories.includes(helper.category));
72
+ }
73
+ if (filter.compatibility) {
74
+ const compatibility = filter.compatibility;
75
+ result = result.filter((helper) => helper.compatibility?.some((c) => compatibility.includes(c)));
76
+ }
77
+ return result;
78
+ }
79
+ /**
80
+ * Load Handlebars helpers.
81
+ */
82
+ load(handlebars, filters) {
83
+ let helpers = this._helpers;
84
+ if (filters) helpers = this.filter(filters);
85
+ for (const helper of helpers) handlebars.registerHelper(helper.name, helper.fn);
86
+ }
87
+ /**
88
+ * Swap Handlebars helpers. This is used when you want to swap out legacy
89
+ * helpers for fumanchu helpers.
90
+ */
91
+ swap(handlebars) {
92
+ for (const helper of this._helpers) handlebars.registerHelper(helper.name, helper.fn);
93
+ }
94
+ };
95
+ //#endregion
22
96
  //#region src/helpers/array.ts
23
97
  const after = (array, n) => {
24
98
  if (!Array.isArray(array)) return [];
@@ -323,7 +397,7 @@ const unique = function(array, options) {
323
397
  }
324
398
  return "";
325
399
  };
326
- const helpers$20 = [
400
+ const helpers$23 = [
327
401
  {
328
402
  name: "after",
329
403
  category: "array",
@@ -488,16 +562,163 @@ const helpers$20 = [
488
562
  }
489
563
  ];
490
564
  //#endregion
491
- //#region src/helpers/code.ts
492
- const embed = (filepath, language) => {
493
- let ext = typeof language === "string" ? language : path.extname(filepath).slice(1);
494
- let code = fs.readFileSync(filepath, "utf8");
495
- if (ext === "markdown" || ext === "md") {
496
- ext = "markdown";
497
- code = code.split("`").join("&#x60");
565
+ //#region src/helpers/html.ts
566
+ const isTagStart = (ch) => ch !== void 0 && (ch === "/" || ch === "!" || ch === "?" || /[a-zA-Z]/.test(ch));
567
+ const stripTags = (str) => {
568
+ let result = "";
569
+ let inTag = false;
570
+ let inComment = false;
571
+ let quote = null;
572
+ for (let i = 0; i < str.length; i++) {
573
+ const ch = str[i];
574
+ if (inComment) {
575
+ if (ch === "-" && str[i + 1] === "-" && str[i + 2] === ">") {
576
+ inComment = false;
577
+ i += 2;
578
+ }
579
+ continue;
580
+ }
581
+ if (inTag) {
582
+ if (quote) {
583
+ if (ch === quote) quote = null;
584
+ } else if (ch === "\"" || ch === "'") quote = ch;
585
+ else if (ch === ">") inTag = false;
586
+ continue;
587
+ }
588
+ if (ch === "<") {
589
+ if (str[i + 1] === "!" && str[i + 2] === "-" && str[i + 3] === "-") {
590
+ inComment = true;
591
+ i += 3;
592
+ continue;
593
+ }
594
+ if (isTagStart(str[i + 1])) {
595
+ inTag = true;
596
+ continue;
597
+ }
598
+ }
599
+ result += ch;
498
600
  }
499
- return `${codeBlock(code, ext).trim()}\n`;
601
+ return result;
602
+ };
603
+ const VOID_ELEMENTS = new Set([
604
+ "area",
605
+ "base",
606
+ "br",
607
+ "col",
608
+ "command",
609
+ "embed",
610
+ "hr",
611
+ "img",
612
+ "input",
613
+ "keygen",
614
+ "link",
615
+ "meta",
616
+ "param",
617
+ "source",
618
+ "track",
619
+ "wbr",
620
+ "circle",
621
+ "ellipse",
622
+ "line",
623
+ "path",
624
+ "polygon",
625
+ "polyline",
626
+ "rect",
627
+ "stop",
628
+ "use"
629
+ ]);
630
+ const tag = (name, attribs = {}, text = "") => {
631
+ let html = `<${name}`;
632
+ for (const [key, val] of Object.entries(attribs)) if (val === true) html += ` ${key}`;
633
+ else if (typeof val === "string" || typeof val === "number") html += ` ${key}="${escapeAttr(String(val))}"`;
634
+ if (VOID_ELEMENTS.has(name.toLowerCase())) return `${html}>`;
635
+ return `${html}>${escapeText(text)}</${name}>`;
636
+ };
637
+ const escapeAttr = (str) => str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
638
+ const escapeText = (str) => str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
639
+ const parseAttributes = (hash = {}) => Object.keys(hash).map((key) => `${key}="${String(hash[key]).replace(/^['"]|["']$/g, "")}"`).join(" ");
640
+ const attr = (options) => {
641
+ const val = parseAttributes(options?.hash ?? {});
642
+ return val.trim() ? ` ${val}` : "";
643
+ };
644
+ const sanitize = (str) => {
645
+ if (typeof str !== "string") return "";
646
+ return stripTags(str).trim();
647
+ };
648
+ const ul = (context, options) => {
649
+ const attrs = parseAttributes(options.hash ?? {});
650
+ const items = context.map((item) => typeof item === "string" ? item : options.fn(item));
651
+ return `${attrs ? `<ul ${attrs}>` : "<ul>"}${items.map((i) => `<li>${i}</li>`).join("\n")}</ul>`;
500
652
  };
653
+ const ol = (context, options) => {
654
+ const attrs = parseAttributes(options.hash ?? {});
655
+ const items = context.map((item) => typeof item === "string" ? item : options.fn(item));
656
+ return `${attrs ? `<ol ${attrs}>` : "<ol>"}${items.map((i) => `<li>${i}</li>`).join("\n")}</ol>`;
657
+ };
658
+ const thumbnailImage = (context) => {
659
+ let figure = "";
660
+ let image = "";
661
+ const link = context.full || false;
662
+ const imageAttributes = {
663
+ alt: context.alt,
664
+ src: context.thumbnail,
665
+ width: context.size.width,
666
+ height: context.size.height
667
+ };
668
+ const figureAttributes = { id: `image-${context.id}` };
669
+ const linkAttributes = {
670
+ href: link,
671
+ rel: "thumbnail"
672
+ };
673
+ /* c8 ignore next -- @preserve */
674
+ if (context.classes) {
675
+ if (context.classes.image) imageAttributes.class = context.classes.image.join(" ");
676
+ if (context.classes.figure) figureAttributes.class = context.classes.figure.join(" ");
677
+ if (context.classes.link) linkAttributes.class = context.classes.link.join(" ");
678
+ }
679
+ figure += `<figure ${parseAttributes(figureAttributes)}>\n`;
680
+ image += `<img ${parseAttributes(imageAttributes)}>\n`;
681
+ if (link) figure += `<a ${parseAttributes(linkAttributes)}>\n${image}</a>\n`;
682
+ else figure += image;
683
+ /* c8 ignore next -- @preserve */
684
+ if (context.caption) figure += `<figcaption>${context.caption}</figcaption>\n`;
685
+ figure += "</figure>";
686
+ return figure;
687
+ };
688
+ const helpers$22 = [
689
+ {
690
+ name: "attr",
691
+ category: "html",
692
+ compatibility: ["browser", "nodejs"],
693
+ fn: attr
694
+ },
695
+ {
696
+ name: "sanitize",
697
+ category: "html",
698
+ compatibility: ["browser", "nodejs"],
699
+ fn: sanitize
700
+ },
701
+ {
702
+ name: "ul",
703
+ category: "html",
704
+ compatibility: ["browser", "nodejs"],
705
+ fn: ul
706
+ },
707
+ {
708
+ name: "ol",
709
+ category: "html",
710
+ compatibility: ["browser", "nodejs"],
711
+ fn: ol
712
+ },
713
+ {
714
+ name: "thumbnailImage",
715
+ category: "html",
716
+ compatibility: ["browser", "nodejs"],
717
+ fn: thumbnailImage
718
+ }
719
+ ];
720
+ //#endregion
721
+ //#region src/helpers/code.ts
501
722
  const gist = (id) => {
502
723
  return tag("script", { src: `https://gist.github.com/${id}.js` });
503
724
  };
@@ -512,26 +733,37 @@ const jsfiddle = (options) => {
512
733
  frameborder
513
734
  });
514
735
  };
515
- const helpers$19 = [
516
- {
517
- name: "embed",
518
- category: "code",
519
- compatibility: ["nodejs"],
520
- fn: embed
521
- },
522
- {
523
- name: "gist",
524
- category: "code",
525
- compatibility: ["browser", "nodejs"],
526
- fn: gist
527
- },
528
- {
529
- name: "jsfiddle",
530
- category: "code",
531
- compatibility: ["browser", "nodejs"],
532
- fn: jsfiddle
736
+ const helpers$21 = [{
737
+ name: "gist",
738
+ category: "code",
739
+ compatibility: ["browser", "nodejs"],
740
+ fn: gist
741
+ }, {
742
+ name: "jsfiddle",
743
+ category: "code",
744
+ compatibility: ["browser", "nodejs"],
745
+ fn: jsfiddle
746
+ }];
747
+ //#endregion
748
+ //#region src/helpers/code.node.ts
749
+ const codeBlock = (code, lang) => {
750
+ return `\`\`\`${lang}\n${code}\n\`\`\`\n`;
751
+ };
752
+ const embed = (filepath, language) => {
753
+ let ext = typeof language === "string" ? language : path.extname(filepath).slice(1);
754
+ let code = fs.readFileSync(filepath, "utf8");
755
+ if (ext === "markdown" || ext === "md") {
756
+ ext = "markdown";
757
+ code = code.split("`").join("&#x60");
533
758
  }
534
- ];
759
+ return `${codeBlock(code, ext).trim()}\n`;
760
+ };
761
+ const helpers$20 = [{
762
+ name: "embed",
763
+ category: "code",
764
+ compatibility: ["nodejs"],
765
+ fn: embed
766
+ }];
535
767
  //#endregion
536
768
  //#region src/helpers/collection.ts
537
769
  const isEmpty = (collection) => {
@@ -553,7 +785,7 @@ const iterate = (collection, fn, inverse) => {
553
785
  }
554
786
  return inverse ? inverse() : "";
555
787
  };
556
- const helpers$18 = [{
788
+ const helpers$19 = [{
557
789
  name: "isEmpty",
558
790
  category: "collection",
559
791
  compatibility: ["browser", "nodejs"],
@@ -645,7 +877,7 @@ const unlessGt = (a, b) => a <= b;
645
877
  const unlessLt = (a, b) => a >= b;
646
878
  const unlessGteq = (a, b) => a < b;
647
879
  const unlessLteq = (a, b) => a > b;
648
- const helpers$17 = [
880
+ const helpers$18 = [
649
881
  {
650
882
  name: "and",
651
883
  category: "comparison",
@@ -926,7 +1158,7 @@ const dateLocale = (dateInput, locale, formatString) => {
926
1158
  if (!parsed.isValid()) throw new Error(`Unable to parse date: ${dateInput}`);
927
1159
  return parsed.locale(locale).format(convertedFormat);
928
1160
  };
929
- const helpers$16 = [
1161
+ const helpers$17 = [
930
1162
  {
931
1163
  name: "year",
932
1164
  category: "date",
@@ -1050,6 +1282,9 @@ const helpers$16 = [
1050
1282
  ];
1051
1283
  //#endregion
1052
1284
  //#region src/helpers/fs.ts
1285
+ const ESCAPE_SEQUENCE = /\\[\s\S]/g;
1286
+ const GLOB_PATTERN = /^!|[*?]|\[[^\]]+\]|\{[^}]+\}|[@?!+*]\([^)]*\)|\([^)]*\|[^)]*\)/;
1287
+ const isGlob = (value) => GLOB_PATTERN.test(value.replace(ESCAPE_SEQUENCE, ""));
1053
1288
  const fileSize = (value, precision) => {
1054
1289
  if (value == null) return "0 B";
1055
1290
  const number = typeof value === "number" ? value : value.length ?? 0;
@@ -1095,7 +1330,7 @@ const readdir = (dir, filter) => {
1095
1330
  }
1096
1331
  return files;
1097
1332
  };
1098
- const helpers$15 = [
1333
+ const helpers$16 = [
1099
1334
  {
1100
1335
  name: "fileSize",
1101
1336
  category: "fs",
@@ -1116,12 +1351,7 @@ const helpers$15 = [
1116
1351
  }
1117
1352
  ];
1118
1353
  //#endregion
1119
- //#region src/helpers/html.ts
1120
- const parseAttributes = (hash = {}) => Object.keys(hash).map((key) => `${key}="${String(hash[key]).replace(/^['"]|["']$/g, "")}"`).join(" ");
1121
- const attr = (options) => {
1122
- const val = parseAttributes(options?.hash ?? {});
1123
- return val.trim() ? ` ${val}` : "";
1124
- };
1354
+ //#region src/helpers/html.node.ts
1125
1355
  const css = function(list, options) {
1126
1356
  if (arguments.length < 2) {
1127
1357
  options = list;
@@ -1151,94 +1381,17 @@ const js = (context) => {
1151
1381
  src: fp
1152
1382
  }) : tag("script", { src: fp })).join("\n");
1153
1383
  };
1154
- const sanitize = (str) => {
1155
- if (typeof str !== "string") return "";
1156
- return striptags(str).trim();
1157
- };
1158
- const ul = (context, options) => {
1159
- const attrs = parseAttributes(options.hash ?? {});
1160
- const items = context.map((item) => typeof item === "string" ? item : options.fn(item));
1161
- return `${attrs ? `<ul ${attrs}>` : "<ul>"}${items.map((i) => `<li>${i}</li>`).join("\n")}</ul>`;
1162
- };
1163
- const ol = (context, options) => {
1164
- const attrs = parseAttributes(options.hash ?? {});
1165
- const items = context.map((item) => typeof item === "string" ? item : options.fn(item));
1166
- return `${attrs ? `<ol ${attrs}>` : "<ol>"}${items.map((i) => `<li>${i}</li>`).join("\n")}</ol>`;
1167
- };
1168
- const thumbnailImage = (context) => {
1169
- let figure = "";
1170
- let image = "";
1171
- const link = context.full || false;
1172
- const imageAttributes = {
1173
- alt: context.alt,
1174
- src: context.thumbnail,
1175
- width: context.size.width,
1176
- height: context.size.height
1177
- };
1178
- const figureAttributes = { id: `image-${context.id}` };
1179
- const linkAttributes = {
1180
- href: link,
1181
- rel: "thumbnail"
1182
- };
1183
- /* c8 ignore next -- @preserve */
1184
- if (context.classes) {
1185
- if (context.classes.image) imageAttributes.class = context.classes.image.join(" ");
1186
- if (context.classes.figure) figureAttributes.class = context.classes.figure.join(" ");
1187
- if (context.classes.link) linkAttributes.class = context.classes.link.join(" ");
1188
- }
1189
- figure += `<figure ${parseAttributes(figureAttributes)}>\n`;
1190
- image += `<img ${parseAttributes(imageAttributes)}>\n`;
1191
- if (link) figure += `<a ${parseAttributes(linkAttributes)}>\n${image}</a>\n`;
1192
- else figure += image;
1193
- /* c8 ignore next -- @preserve */
1194
- if (context.caption) figure += `<figcaption>${context.caption}</figcaption>\n`;
1195
- figure += "</figure>";
1196
- return figure;
1197
- };
1198
- const helpers$14 = [
1199
- {
1200
- name: "attr",
1201
- category: "html",
1202
- compatibility: ["browser", "nodejs"],
1203
- fn: attr
1204
- },
1205
- {
1206
- name: "css",
1207
- category: "html",
1208
- compatibility: ["nodejs"],
1209
- fn: css
1210
- },
1211
- {
1212
- name: "js",
1213
- category: "html",
1214
- compatibility: ["nodejs"],
1215
- fn: js
1216
- },
1217
- {
1218
- name: "sanitize",
1219
- category: "html",
1220
- compatibility: ["browser", "nodejs"],
1221
- fn: sanitize
1222
- },
1223
- {
1224
- name: "ul",
1225
- category: "html",
1226
- compatibility: ["browser", "nodejs"],
1227
- fn: ul
1228
- },
1229
- {
1230
- name: "ol",
1231
- category: "html",
1232
- compatibility: ["browser", "nodejs"],
1233
- fn: ol
1234
- },
1235
- {
1236
- name: "thumbnailImage",
1237
- category: "html",
1238
- compatibility: ["browser", "nodejs"],
1239
- fn: thumbnailImage
1240
- }
1241
- ];
1384
+ const helpers$15 = [{
1385
+ name: "css",
1386
+ category: "html",
1387
+ compatibility: ["nodejs"],
1388
+ fn: css
1389
+ }, {
1390
+ name: "js",
1391
+ category: "html",
1392
+ compatibility: ["nodejs"],
1393
+ fn: js
1394
+ }];
1242
1395
  //#endregion
1243
1396
  //#region src/utils.ts
1244
1397
  /**
@@ -1316,7 +1469,7 @@ const i18n = function(prop, locals, options) {
1316
1469
  if (typeof result === "undefined") throw new Error("{{i18n}} helper cannot find property \"" + String(prop) + "\" for language \"" + lang + "\"");
1317
1470
  return String(result);
1318
1471
  };
1319
- const helpers$13 = [{
1472
+ const helpers$14 = [{
1320
1473
  name: "i18n",
1321
1474
  category: "i18n",
1322
1475
  compatibility: ["browser", "nodejs"],
@@ -1344,7 +1497,7 @@ const ordinalize = (value) => {
1344
1497
  default: return `${str}th`;
1345
1498
  }
1346
1499
  };
1347
- const helpers$12 = [{
1500
+ const helpers$13 = [{
1348
1501
  name: "inflect",
1349
1502
  category: "inflection",
1350
1503
  compatibility: ["browser", "nodejs"],
@@ -1472,7 +1625,7 @@ function switchOutput(type, json) {
1472
1625
  default: return json;
1473
1626
  }
1474
1627
  }
1475
- const helpers$11 = [
1628
+ const helpers$12 = [
1476
1629
  {
1477
1630
  name: "log",
1478
1631
  category: "logging",
@@ -1555,7 +1708,7 @@ const mm = (...args) => {
1555
1708
  console.log("helper instead.");
1556
1709
  return match(...args);
1557
1710
  };
1558
- const helpers$10 = [
1711
+ const helpers$11 = [
1559
1712
  {
1560
1713
  name: "match",
1561
1714
  category: "match",
@@ -1637,7 +1790,7 @@ const sum = (...values) => {
1637
1790
  return total;
1638
1791
  };
1639
1792
  const times = (a, b) => multiply(a, b);
1640
- const helpers$9 = [
1793
+ const helpers$10 = [
1641
1794
  {
1642
1795
  name: "abs",
1643
1796
  category: "math",
@@ -1737,6 +1890,18 @@ const helpers$9 = [
1737
1890
  ];
1738
1891
  //#endregion
1739
1892
  //#region src/helpers/md.ts
1893
+ const md = new MarkdownIt({
1894
+ breaks: true,
1895
+ html: true,
1896
+ langPrefix: "lang-",
1897
+ typographer: false,
1898
+ xhtmlOut: false
1899
+ });
1900
+ function renderString(input) {
1901
+ return new HandlebarsLib.SafeString(ent.decode(md.render(input)));
1902
+ }
1903
+ //#endregion
1904
+ //#region src/helpers/md.node.ts
1740
1905
  const renderMarkdown = function(input, options) {
1741
1906
  if (typeof input === "object" && input && "fn" in input && typeof input.fn === "function") {
1742
1907
  options = input;
@@ -1746,21 +1911,14 @@ const renderMarkdown = function(input, options) {
1746
1911
  cwd: process$1.cwd(),
1747
1912
  ...options
1748
1913
  };
1749
- const md = new Remarkable({
1750
- breaks: true,
1751
- html: true,
1752
- langPrefix: "lang-",
1753
- typographer: false,
1754
- xhtmlOut: false
1755
- });
1756
1914
  let string_ = input;
1757
- if (string_ && string_.length > 0) {
1915
+ if (string_.length > 0) {
1758
1916
  const filepath = path.resolve(options_.cwd, string_);
1759
1917
  if (fs.existsSync(filepath) && fs.statSync(filepath).isFile()) string_ = fs.readFileSync(filepath, "utf8");
1760
1918
  }
1761
- return new HandlebarsLib.SafeString(ent.decode(md.render(string_)));
1919
+ return renderString(string_);
1762
1920
  };
1763
- const helpers$8 = [{
1921
+ const helpers$9 = [{
1764
1922
  name: "md",
1765
1923
  category: "markdown",
1766
1924
  compatibility: ["nodejs"],
@@ -1773,6 +1931,68 @@ const helpers$8 = [{
1773
1931
  }];
1774
1932
  //#endregion
1775
1933
  //#region src/helpers/misc.ts
1934
+ const objectToString = Object.prototype.toString;
1935
+ function ctorName(val) {
1936
+ return typeof val.constructor === "function" ? val.constructor.name : null;
1937
+ }
1938
+ function isGeneratorFn(val) {
1939
+ return ctorName(val) === "GeneratorFunction";
1940
+ }
1941
+ function isGeneratorObj(val) {
1942
+ return typeof val.throw === "function" && typeof val.return === "function" && typeof val.next === "function";
1943
+ }
1944
+ function isDateLike(val) {
1945
+ return typeof val.toDateString === "function" && typeof val.getDate === "function" && typeof val.setDate === "function";
1946
+ }
1947
+ function isErrorLike(val) {
1948
+ return typeof val.message === "string" && val.constructor && typeof val.constructor.stackTraceLimit === "number";
1949
+ }
1950
+ function isRegexpLike(val) {
1951
+ return typeof val.flags === "string" && typeof val.ignoreCase === "boolean" && typeof val.multiline === "boolean" && typeof val.global === "boolean";
1952
+ }
1953
+ function kindOf(val) {
1954
+ if (val === void 0) return "undefined";
1955
+ if (val === null) return "null";
1956
+ const type = typeof val;
1957
+ if (type === "boolean") return "boolean";
1958
+ if (type === "string") return "string";
1959
+ if (type === "number") return "number";
1960
+ if (type === "symbol") return "symbol";
1961
+ if (type === "bigint") return "bigint";
1962
+ if (type === "function") return isGeneratorFn(val) ? "generatorfunction" : "function";
1963
+ if (Array.isArray(val)) return "array";
1964
+ if (typeof Buffer !== "undefined" && Buffer.isBuffer?.(val)) return "buffer";
1965
+ if (typeof val.length === "number" && objectToString.call(val) === "[object Arguments]") return "arguments";
1966
+ if (val instanceof Date || isDateLike(val)) return "date";
1967
+ if (val instanceof Error || isErrorLike(val)) return "error";
1968
+ if (val instanceof RegExp || isRegexpLike(val)) return "regexp";
1969
+ switch (ctorName(val)) {
1970
+ case "Promise": return "promise";
1971
+ case "WeakMap": return "weakmap";
1972
+ case "WeakSet": return "weakset";
1973
+ case "Map": return "map";
1974
+ case "Set": return "set";
1975
+ case "Int8Array": return "int8array";
1976
+ case "Uint8Array": return "uint8array";
1977
+ case "Uint8ClampedArray": return "uint8clampedarray";
1978
+ case "Int16Array": return "int16array";
1979
+ case "Uint16Array": return "uint16array";
1980
+ case "Int32Array": return "int32array";
1981
+ case "Uint32Array": return "uint32array";
1982
+ case "Float32Array": return "float32array";
1983
+ case "Float64Array": return "float64array";
1984
+ }
1985
+ if (isGeneratorObj(val)) return "generator";
1986
+ const tag = objectToString.call(val);
1987
+ switch (tag) {
1988
+ case "[object Object]": return "object";
1989
+ case "[object Map Iterator]": return "mapiterator";
1990
+ case "[object Set Iterator]": return "setiterator";
1991
+ case "[object String Iterator]": return "stringiterator";
1992
+ case "[object Array Iterator]": return "arrayiterator";
1993
+ }
1994
+ return tag.slice(8, -1).toLowerCase().replace(/\s/g, "");
1995
+ }
1776
1996
  /**
1777
1997
  * Block helper for exposing private `@` variables on the context
1778
1998
  * Replaces the external handlebars-helper-create-frame package
@@ -1798,7 +2018,7 @@ function withHash(options) {
1798
2018
  if (options.hash && Object.keys(options.hash).length) return options.fn(options.hash);
1799
2019
  return options.inverse(this);
1800
2020
  }
1801
- const helpers$7 = [
2021
+ const helpers$8 = [
1802
2022
  {
1803
2023
  name: "frame",
1804
2024
  category: "misc",
@@ -1901,7 +2121,7 @@ const toPrecision = (value, precision) => {
1901
2121
  const prec = typeof precision === "number" && !Number.isNaN(precision) ? precision : 1;
1902
2122
  return Number(num).toPrecision(prec);
1903
2123
  };
1904
- const helpers$6 = [
2124
+ const helpers$7 = [
1905
2125
  {
1906
2126
  name: "bytes",
1907
2127
  category: "number",
@@ -2041,7 +2261,7 @@ const pick = (props, context, options) => {
2041
2261
  }
2042
2262
  return result;
2043
2263
  };
2044
- const helpers$5 = [
2264
+ const helpers$6 = [
2045
2265
  {
2046
2266
  name: "extend",
2047
2267
  category: "object",
@@ -2179,7 +2399,7 @@ const segments = (filepath, a, b) => {
2179
2399
  const end = Number(b);
2180
2400
  return fp.split(/[\\/]+/).slice(start, end).join("/");
2181
2401
  };
2182
- const helpers$4 = [
2402
+ const helpers$5 = [
2183
2403
  {
2184
2404
  name: "absolute",
2185
2405
  category: "path",
@@ -2240,7 +2460,7 @@ const test = (str, regex) => {
2240
2460
  if (!(regex instanceof RegExp)) throw new TypeError("expected a regular expression");
2241
2461
  return regex.test(str);
2242
2462
  };
2243
- const helpers$3 = [{
2463
+ const helpers$4 = [{
2244
2464
  name: "toRegex",
2245
2465
  category: "regex",
2246
2466
  compatibility: ["browser", "nodejs"],
@@ -2397,7 +2617,7 @@ const raw = (options) => {
2397
2617
  if (options && typeof options.fn === "function") return options.fn();
2398
2618
  return "";
2399
2619
  };
2400
- const helpers$2 = [
2620
+ const helpers$3 = [
2401
2621
  {
2402
2622
  name: "append",
2403
2623
  category: "string",
@@ -2621,9 +2841,6 @@ const all = ["browser", "nodejs"];
2621
2841
  const encodeUri = (str) => {
2622
2842
  if (typeof str === "string") return encodeURIComponent(str);
2623
2843
  };
2624
- const escapeFn = (str) => {
2625
- if (typeof str === "string") return escape(str);
2626
- };
2627
2844
  const decodeUri = (str) => {
2628
2845
  if (typeof str === "string") return decodeURIComponent(str);
2629
2846
  };
@@ -2633,31 +2850,16 @@ function urlEncode(...args) {
2633
2850
  function urlDecode(...args) {
2634
2851
  return decodeUri.apply(this, args);
2635
2852
  }
2636
- const urlResolve = (base, href) => resolve(String(base), String(href));
2637
- const urlParse = (str) => parse(String(str));
2638
2853
  const stripQuerystring = (str) => {
2639
2854
  if (typeof str === "string") return str.split("?")[0];
2640
2855
  };
2641
- const stripProtocol = (str) => {
2642
- if (typeof str === "string") {
2643
- const parsed = parse(str);
2644
- parsed.protocol = "";
2645
- return format(parsed);
2646
- }
2647
- };
2648
- const helpers$1 = [
2856
+ const helpers$2 = [
2649
2857
  {
2650
2858
  name: "encodeURI",
2651
2859
  category: "url",
2652
2860
  compatibility: all,
2653
2861
  fn: encodeUri
2654
2862
  },
2655
- {
2656
- name: "escape",
2657
- category: "url",
2658
- compatibility: ["nodejs"],
2659
- fn: escapeFn
2660
- },
2661
2863
  {
2662
2864
  name: "decodeURI",
2663
2865
  category: "url",
@@ -2676,6 +2878,34 @@ const helpers$1 = [
2676
2878
  compatibility: all,
2677
2879
  fn: urlDecode
2678
2880
  },
2881
+ {
2882
+ name: "stripQuerystring",
2883
+ category: "url",
2884
+ compatibility: all,
2885
+ fn: stripQuerystring
2886
+ }
2887
+ ];
2888
+ //#endregion
2889
+ //#region src/helpers/url.node.ts
2890
+ const escapeFn = (str) => {
2891
+ if (typeof str === "string") return escape(str);
2892
+ };
2893
+ const urlResolve = (base, href) => resolve(String(base), String(href));
2894
+ const urlParse = (str) => parse(String(str));
2895
+ const stripProtocol = (str) => {
2896
+ if (typeof str === "string") {
2897
+ const parsed = parse(str);
2898
+ parsed.protocol = "";
2899
+ return format(parsed);
2900
+ }
2901
+ };
2902
+ const helpers$1 = [
2903
+ {
2904
+ name: "escape",
2905
+ category: "url",
2906
+ compatibility: ["nodejs"],
2907
+ fn: escapeFn
2908
+ },
2679
2909
  {
2680
2910
  name: "urlResolve",
2681
2911
  category: "url",
@@ -2688,12 +2918,6 @@ const helpers$1 = [
2688
2918
  compatibility: ["nodejs"],
2689
2919
  fn: urlParse
2690
2920
  },
2691
- {
2692
- name: "stripQuerystring",
2693
- category: "url",
2694
- compatibility: all,
2695
- fn: stripQuerystring
2696
- },
2697
2921
  {
2698
2922
  name: "stripProtocol",
2699
2923
  category: "url",
@@ -2703,104 +2927,35 @@ const helpers$1 = [
2703
2927
  ];
2704
2928
  //#endregion
2705
2929
  //#region src/helper-registry.ts
2706
- var HelperRegistry = class {
2707
- _helpers = [];
2708
- constructor() {
2709
- this.init();
2710
- }
2711
- /**
2712
- * Get all registered helpers.
2713
- * @returns {Helper[]} The array of registered helpers.
2714
- */
2715
- get helpers() {
2716
- return this._helpers;
2717
- }
2718
- /**
2719
- * Initialize the helper registry. This is performed during the construction of the registry such as new HelperRegistry().
2720
- */
2930
+ var HelperRegistry = class extends HelperRegistryBase {
2721
2931
  init() {
2722
- this.registerHelpers(helpers$20);
2723
- this.registerHelpers(helpers$18);
2932
+ this.registerHelpers(helpers$23);
2933
+ this.registerHelpers(helpers$19);
2934
+ this.registerHelpers(helpers$17);
2724
2935
  this.registerHelpers(helpers$16);
2936
+ this.registerHelpers(helpers$21);
2937
+ this.registerHelpers(helpers$20);
2938
+ this.registerHelpers(helpers$9);
2939
+ this.registerHelpers(helpers$22);
2725
2940
  this.registerHelpers(helpers$15);
2726
- this.registerHelpers(helpers$19);
2727
- this.registerHelpers(helpers$8);
2941
+ this.registerHelpers(helpers$18);
2728
2942
  this.registerHelpers(helpers$14);
2729
- this.registerHelpers(helpers$17);
2730
2943
  this.registerHelpers(helpers$13);
2731
2944
  this.registerHelpers(helpers$12);
2732
2945
  this.registerHelpers(helpers$11);
2733
2946
  this.registerHelpers(helpers$10);
2734
- this.registerHelpers(helpers$9);
2947
+ this.registerHelpers(helpers$8);
2735
2948
  this.registerHelpers(helpers$7);
2736
- this.registerHelpers(helpers$6);
2949
+ this.registerHelpers(helpers$5);
2737
2950
  this.registerHelpers(helpers$4);
2738
2951
  this.registerHelpers(helpers$3);
2739
2952
  this.registerHelpers(helpers$2);
2740
2953
  this.registerHelpers(helpers$1);
2741
- this.registerHelpers(helpers$5);
2742
- }
2743
- /**
2744
- * Register a helper.
2745
- * @param {Helper} helper The helper to register.
2746
- * @returns True if the helper was registered, false otherwise.
2747
- */
2748
- register(helper) {
2749
- const result = false;
2750
- if (!this.has(helper.name)) this._helpers.push(helper);
2751
- return result;
2752
- }
2753
- /**
2754
- * Register multiple helpers.
2755
- * @param {Helper[]} helpers The array of helpers to register.
2756
- */
2757
- registerHelpers(helpers) {
2758
- for (const helper of helpers) this.register(helper);
2759
- }
2760
- /**
2761
- * Check if a helper is registered.
2762
- * @param name The name of the helper.
2763
- * @returns True if the helper is registered, false otherwise.
2764
- */
2765
- has(name) {
2766
- return this._helpers.some((helper) => helper.name === name);
2767
- }
2768
- /**
2769
- * Filter helpers by the given criteria.
2770
- * @param filter The filter criteria.
2771
- * @returns The filtered helpers.
2772
- */
2773
- filter(filter) {
2774
- let result = this._helpers;
2775
- if (filter.names) result = result.filter((helper) => filter.names?.includes(helper.name));
2776
- if (filter.categories) result = result.filter((helper) => filter.categories?.includes(helper.category));
2777
- if (filter.compatibility) result = result.filter((helper) => helper.compatibility?.some((c) => filter.compatibility?.includes(c)));
2778
- return result;
2779
- }
2780
- /**
2781
- * Load Handlebars helpers.
2782
- * @param {Handlebars} handlebars The Handlebars instance.
2783
- * @returns {void}
2784
- */
2785
- load(handlebars, filters) {
2786
- let helpers = this._helpers;
2787
- if (filters) helpers = this.filter(filters);
2788
- for (const helper of helpers) handlebars.registerHelper(helper.name, helper.fn);
2789
- }
2790
- /**
2791
- * Swap Handlebars helpers. This is used when you want to swap out legacy helpers for fumanchu helpers.
2792
- * @param handlebars The Handlebars instance.
2793
- * @returns {void}
2794
- */
2795
- swap(handlebars) {
2796
- for (const helper of this._helpers) {
2797
- handlebars.unregisterHelper(helper.name);
2798
- handlebars.registerHelper(helper.name, helper.fn);
2799
- }
2954
+ this.registerHelpers(helpers$6);
2800
2955
  }
2801
2956
  };
2802
2957
  //#endregion
2803
- //#region src/index.ts
2958
+ //#region src/index.node.ts
2804
2959
  /**
2805
2960
  * Handlebars library not initiated with helpers
2806
2961
  * @type {Handlebars}
@@ -2854,7 +3009,7 @@ function fumanchu(options) {
2854
3009
  else cache = new CacheableMemory({ useClone: false });
2855
3010
  const originalCompile = handlebars.compile.bind(handlebars);
2856
3011
  handlebars.compile = (input, compileOptions) => {
2857
- const key = compileOptions ? input + JSON.stringify(compileOptions) : String(input);
3012
+ const key = compileOptions ? `${input}\0${JSON.stringify(compileOptions)}` : input;
2858
3013
  const cached = cache.get(key);
2859
3014
  if (cached) return cached;
2860
3015
  const compiled = originalCompile(input, compileOptions);